diff options
Diffstat (limited to 'winery/org.eclipse.winery.topologymodeler/src/main')
86 files changed, 27078 insertions, 0 deletions
diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/WineryUtil.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/WineryUtil.java new file mode 100644 index 0000000..2dc7e27 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/WineryUtil.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation + *******************************************************************************/ +package org.eclipse.winery.topologymodeler; + +import java.util.List; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.eclipse.winery.common.interfaces.QNameWithName; + +public class WineryUtil { + + /** + * LocalName is the ID of the element, whereas Name is the speaking name + */ + public static class LocalNameNamePair implements Comparable<LocalNameNamePair> { + + String localName; + String name; + + + public LocalNameNamePair(String localName, String name) { + this.localName = localName; + this.name = name; + } + + public String getLocalName() { + return this.localName; + } + + public String getName() { + return this.name; + } + + /** + * Ordering according to name + */ + @Override + public int compareTo(LocalNameNamePair otherPair) { + return this.name.compareTo(otherPair.name); + } + + @Override + public int hashCode() { + return this.localName.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof LocalNameNamePair) { + return this.localName.equals(((LocalNameNamePair) o).getLocalName()); + } else { + return false; + } + } + } + + + public static SortedMap<String, SortedSet<LocalNameNamePair>> convertQNameWithNameListToNamespaceToLocalNameNamePairList(List<QNameWithName> list) { + if (list == null) { + throw new IllegalArgumentException("list may not be null"); + } + SortedMap<String, SortedSet<LocalNameNamePair>> res = new TreeMap<>(); + for (QNameWithName qnameWithName : list) { + SortedSet<LocalNameNamePair> localNameNamePairSet = res.get(qnameWithName.qname.getNamespaceURI()); + if (localNameNamePairSet == null) { + localNameNamePairSet = new TreeSet<>(); + res.put(qnameWithName.qname.getNamespaceURI(), localNameNamePairSet); + } + LocalNameNamePair pair = new LocalNameNamePair(qnameWithName.qname.getLocalPart(), qnameWithName.name); + localNameNamePairSet.add(pair); + } + return res; + } + +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/DeferredAnalyzer.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/DeferredAnalyzer.java new file mode 100644 index 0000000..c4db9bb --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/DeferredAnalyzer.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Constants; + +/** + * This class contains a method to analyze a TOSCA {@link TTopologyTemplate} for + * the occurrence of "Deferred"-{@link TRelationshipTemplate}s. + * + * A "Deferred"-{@link TRelationshipTemplate} serves as place holder for any number of Node or Relationship + * Templates. + */ +public class DeferredAnalyzer { + + /** + * Iterates over all {@link TRelationshipTemplate} and checks if its type is "deferred". + * + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return a list of found deferred {@link TRelationshipTemplate}s + */ + public static List<TRelationshipTemplate> analyzeDeferredRelations(TOSCAAnalyzer toscaAnalyzer) { + + List<TRelationshipTemplate> foundDeferredRelations = new ArrayList<TRelationshipTemplate>(); + + for (TRelationshipTemplate relationshipTemplate : toscaAnalyzer.getRelationshipTemplates()) { + if (relationshipTemplate.getType() != null && relationshipTemplate.getType().getLocalPart().equals(Constants.DEFERRED_QNAME.getLocalPart()) && + relationshipTemplate.getType().getNamespaceURI().equals(Constants.DEFERRED_QNAME.getNamespaceURI())) { + + // TODO: This step has to be done until the "Provisioning-API" + // is implemented. The Deferred RelationshipTemplate can only be + // completed if Requirements exist at the source template. + TNodeTemplate source = (TNodeTemplate) relationshipTemplate.getSourceElement().getRef(); + + if (source.getRequirements() != null) { + foundDeferredRelations.add(relationshipTemplate); + } + } + } + return foundDeferredRelations; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/PlaceHolderAnalyzer.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/PlaceHolderAnalyzer.java new file mode 100644 index 0000000..ddf0e91 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/PlaceHolderAnalyzer.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Constants; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Utils; + +/** + * This class analyzes the occurrence of place holders in a topology and writes them to an {@link ArrayList}. + */ +public class PlaceHolderAnalyzer { + + /** + * This method searches {@link TNodeTemplate}s that are derived from the abstract "PlaceHolder" type and adds them to a list. + * + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return the found place holders of the topology as a list. + */ + public static List<TNodeTemplate> analyzePlaceHolders(TOSCAAnalyzer toscaAnalyzer) { + + List<TNodeTemplate> foundPlaceHolders = new ArrayList<TNodeTemplate>(); + + // Check the type of the NodeTemplates, write them to a list if the type is derived from the common place holder type. + for (TNodeTemplate nodeTemplate : toscaAnalyzer.getNodeTemplates()) { + + TNodeType nodeType = Utils.getNodeTypeForId(toscaAnalyzer.getNodeTypes(), nodeTemplate.getType()); + + if (nodeType != null && nodeType.getDerivedFrom() != null && nodeType.getDerivedFrom().getTypeRef().getLocalPart().equals(Constants.PLACE_HOLDER_QNAME.getLocalPart()) && + nodeType.getDerivedFrom().getTypeRef().getNamespaceURI().equals(Constants.PLACE_HOLDER_QNAME.getNamespaceURI())) { + foundPlaceHolders.add(nodeTemplate); + } + } + return foundPlaceHolders; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/RequirementAnalyzer.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/RequirementAnalyzer.java new file mode 100644 index 0000000..71f3961 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/RequirementAnalyzer.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.winery.model.tosca.TCapability; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRequirement; +import org.eclipse.winery.model.tosca.TRequirementDefinition; +import org.eclipse.winery.model.tosca.TRequirementType; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Utils; + +/** + * This class analyzes the occurrence of TOSCA {@link TRequirement}s in a {@link TTopologyTemplate} and checks whether they + * are already fulfilled or not. + */ +public class RequirementAnalyzer { + + /** + * This method checks if {@link TNodeTemplate}s contain {@link TRequirement}s and adds them to a {@link Map}. + * + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return a map containing {@link TNodeTemplate}s and their {@link TRequirement}s + */ + public static Map<TRequirement, TNodeTemplate> analyzeRequirements(TOSCAAnalyzer toscaAnalyzer) { + + // map containing entries for a Requirement and its corresponding NodeTemplate + Map<TRequirement, TNodeTemplate> unfulfilledRequirements = new HashMap<TRequirement, TNodeTemplate>(); + + for (TNodeTemplate nodeTemplate : toscaAnalyzer.getNodeTemplates()) { + + List<TRequirement> requirements = new ArrayList<>(); + + TNodeType nodeType = Utils.getNodeTypeForId(toscaAnalyzer.getNodeTypes(), nodeTemplate.getType()); + + if (nodeType.getRequirementDefinitions() != null && !nodeType.getRequirementDefinitions().getRequirementDefinition().isEmpty()) { + + List<TRequirementDefinition> requirementDefinitions = nodeType.getRequirementDefinitions().getRequirementDefinition(); + + // check the requirements of the type of the used NodeTemplate + for (TRequirementDefinition requirementDefinition: requirementDefinitions) { + TRequirement requirement = new TRequirement(); + requirement.setType(requirementDefinition.getRequirementType()); + requirement.setName(requirementDefinition.getName()); + requirement.setId(Utils.createRandomID()); + } + } + + if (nodeTemplate.getRequirements() != null && !nodeTemplate.getRequirements().getRequirement().isEmpty()) { + requirements.addAll(nodeTemplate.getRequirements().getRequirement()); + } + + if (!requirements.isEmpty()) { + // list containing the RelationshipTemplates connecting to the NodeTemplate + List<TRelationshipTemplate> connectors = new ArrayList<TRelationshipTemplate>(); + + // add the connected RelationshipTemplates + for (TRelationshipTemplate connector : toscaAnalyzer.getRelationshipTemplates()) { + if (connector.getSourceElement().getRef().equals(nodeTemplate)) { + connectors.add(connector); + } + } + + // add requirements of unconnected NodeTemplates to the map because they can't be fulfilled + if (connectors.size() == 0) { + for (TRequirement requirement : requirements) { + unfulfilledRequirements.put(requirement, nodeTemplate); + } + } else { + boolean fulfilled = false; + + // check if one of the connected NodeTemplates already fulfill the requirement + for (TRequirement requirement : requirements) { + for (TRelationshipTemplate connector : connectors) { + TNodeTemplate connectedNodeTemplate = (TNodeTemplate) connector.getTargetElement().getRef(); + if (connectedNodeTemplate.getCapabilities() != null) { + for (TCapability capa : connectedNodeTemplate.getCapabilities().getCapability()) { + for (TRequirementType reqType : toscaAnalyzer.getRequirementTypes()) { + if (requirement.getType().getLocalPart().equals(reqType.getName())) { + if (reqType.getRequiredCapabilityType().getLocalPart().equals(capa.getType().getLocalPart()) + && reqType.getRequiredCapabilityType().getNamespaceURI().equals(capa.getType().getNamespaceURI())) { + fulfilled = true; + } + } + } + } + } + } + if (!fulfilled) { + unfulfilledRequirements.put(requirement, nodeTemplate); + } + } + } + } + } + return unfulfilledRequirements; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/TOSCAAnalyzer.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/TOSCAAnalyzer.java new file mode 100644 index 0000000..45131a7 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/TOSCAAnalyzer.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.winery.model.tosca.TEntityTemplate; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRelationshipType; +import org.eclipse.winery.model.tosca.TRequirement; +import org.eclipse.winery.model.tosca.TRequirementType; +import org.eclipse.winery.model.tosca.TTopologyTemplate; + +/** + * This class contains several methods to analyze the content of a TOSCA {@link TTopologyTemplate} and to fill a data model + * with the analyzed information. This class serves the access to all types and templates of a topology. + */ +public class TOSCAAnalyzer { + + // lists containing the elements of a topology + List<TNodeTemplate> nodeTemplates = new ArrayList<TNodeTemplate>(); + List<TRelationshipTemplate> relationshipTemplates = new ArrayList<TRelationshipTemplate>(); + List<TRequirement> requirements = new ArrayList<TRequirement>(); + + List<TNodeType> nodeTypes; + List<TRelationshipType> relationshipTypes; + List<TRequirementType> requirementTypes; + + /** + * This method analyzes the TOSCA {@link TTopologyTemplate} for {@link TNodeTemplate}s, {@link TRelationshipTemplate}s + * and existing {@link TRequirement}s and adds them to a list. + * + * @param topology + * the TOSCA {@link TTopologyTemplate} + */ + public void analyzeTOSCATopology(TTopologyTemplate topology) { + + // fill the data model with content of the topology + List<TEntityTemplate> templateNodes = topology.getNodeTemplateOrRelationshipTemplate(); + + for (TEntityTemplate entityTemplate : templateNodes) { + if (entityTemplate instanceof TNodeTemplate) { + // add the node templates and their requirements to the data model + nodeTemplates.add((TNodeTemplate) entityTemplate); + if (((TNodeTemplate) entityTemplate).getRequirements() != null) { + requirements.addAll(((TNodeTemplate) entityTemplate).getRequirements().getRequirement()); + } + } else if (entityTemplate instanceof TRelationshipTemplate) { + // add RelationshipTemplates + relationshipTemplates.add((TRelationshipTemplate) entityTemplate); + } + } + } + + /** + * Setter for the types received from the Winery repository. + * + * @param nodeTypeXMLStrings + * a list of {@link TNodeType}s from the Winery repository + * @param relationshipTypeXMLStrings + * a list of {@link TRelationshipType}s from the Winery repository + * @param requirementTypeList + * a list of {@link TRequirementType}s from the Winery repository + */ + public void setTypes(List<TNodeType> nodeTypes, List<TRelationshipType> relationshipTypes, List<TRequirementType> requirementTypes) { + this.nodeTypes = nodeTypes; + this.relationshipTypes = relationshipTypes; + this.requirementTypes = requirementTypes; + } + + /** + * Returns the {@link TNodeTemplate}s of the topology. + * + * @return the {@link TNodeTemplate}s as a list + */ + public List<TNodeTemplate> getNodeTemplates() { + return nodeTemplates; + } + + /** + * Returns the {@link TRelationshipTemplate}s of the topology. + * + * @return the {@link TRelationshipTemplate}s as a list + */ + public List<TRelationshipTemplate> getRelationshipTemplates() { + return relationshipTemplates; + } + + /** + * Returns the {@link TRequirement}s of the topology. + * + * @return the {@link TRequirement}s as a list + */ + public List<TRequirement> getRequirements() { + return requirements; + } + + /** + * Returns the {@link TRelationshipType}s of the topology. + * + * @return the {@link TRelationshipType}s as a list + */ + public List<TRelationshipType> getRelationshipTypes() { + return relationshipTypes; + } + + /** + * Returns the {@link TNodeType}s of the topology. + * + * @return the {@link TNodeType}s as a list + */ + public List<TNodeType> getNodeTypes() { + return nodeTypes; + } + + /** + * Returns the {@link TRequirementType}s of the topology. + * + * @return the {@link TRequirementType}s as a list + */ + public List<TRequirementType> getRequirementTypes() { + return requirementTypes; + } + + /** + * Clears all the templates from the data model before the analysis of a topology is restarted. + */ + public void clear() { + nodeTemplates.clear(); + relationshipTemplates.clear(); + requirements.clear(); + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/package-info.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/package-info.java new file mode 100644 index 0000000..5333b5e --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/analyzer/package-info.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +/** + * This package contains classes and methods to analyze a TOSCA {@link org.opentosca.model.tosca.TTopologyTemplate} for its content. + */ +package org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer; + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/Constants.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/Constants.java new file mode 100644 index 0000000..e45c11a --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/Constants.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.helper; + +import javax.xml.namespace.QName; + +import org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.CompletionInterface; + +/** + * This class contains several constants used by the completion add-on. + */ +public class Constants { + + /** + * Constant for the QName of the "deferred" type. + */ + public static final QName DEFERRED_QNAME = new QName("http://www.opentosca.org", "deferred"); + + /** + * Constant for the QName of the "PlaceHolder" type. + */ + public static final QName PLACE_HOLDER_QNAME = new QName("http://www.opentosca.org", "PlaceHolder"); + + /** + * Contains possible types of expandable place holders. + */ + public enum PlaceHolders { + WEBSERVER, DATABASE, OPERATINGSYSTEM, CLOUDPROVIDER; + + /** + * Overwritten toString() method to return formatted strings. + */ + public String toString() { + + switch (this) { + case WEBSERVER: + return "Webserver"; + case DATABASE: + return "Database"; + case OPERATINGSYSTEM: + return "OperatingSystem"; + case CLOUDPROVIDER: + return "CloudProvider"; + default: + return null; + } + } + } + + /** + * The messages returned by the {@link CompletionInterface} + */ + public enum CompletionMessages { + TOPOLOGYCOMPLETE, USERINTERACTION, STEPBYSTEP, SUCCESS, USERTOPOLOGYSELECTION, FAILURE; + + /** + * Overwritten toString() method to return formatted strings. + */ + public String toString() { + + switch (this) { + case TOPOLOGYCOMPLETE: + return "topologyComplete"; + case USERINTERACTION: + return "userInteraction"; + case STEPBYSTEP: + return "stepByStep"; + case SUCCESS: + return "success"; + case USERTOPOLOGYSELECTION: + return "userTopologySelection"; + case FAILURE: + return "failure"; + default: + return null; + } + } + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/JAXBHelper.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/JAXBHelper.java new file mode 100644 index 0000000..84a3e28 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/JAXBHelper.java @@ -0,0 +1,374 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.helper; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; + +import org.eclipse.winery.common.ModelUtilities; +import org.eclipse.winery.common.Util; +import org.eclipse.winery.model.tosca.Definitions; +import org.eclipse.winery.model.tosca.TDefinitions; +import org.eclipse.winery.model.tosca.TEntityTemplate; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRelationshipTemplate.SourceElement; +import org.eclipse.winery.model.tosca.TServiceTemplate; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; + +/** + * This class contains methods for marshalling and unmarshalling a topology XML string via JAXB. + * + */ +public class JAXBHelper { + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JAXBHelper.class.getName()); + + /** + * This constant is used in the buildXML method which add coordinates to Node Templates so they + * are arranged properly in the Winery topology modeler. + * + * The x coordinate is constant because it is assumed that a stack of NodeTemplates is displayed. + */ + private static final String NODETEMPLATE_X_COORDINATE = "500"; + + /** + * This method creates an JAXB Unmarshaller used by the methods contained in this class. + * + * @return the JAXB unmarshaller object + * + * @throws JAXBException + * this exception can occur when the JAXBContext is created + */ + private static Unmarshaller createUnmarshaller() throws JAXBException { + // initiate JaxB context + JAXBContext context; + context = JAXBContext.newInstance(Definitions.class); + + return context.createUnmarshaller(); + } + + /** + * This method returns a {@link TTopologyTemplate} given as XML string as JaxBObject. + * + * @param xmlString + * the {@link TTopologyTemplate} to be unmarshalled + * + * @return the unmarshalled {@link TTopologyTemplate} + */ + public static TTopologyTemplate getTopologyAsJaxBObject(String xmlString) { + try { + + logger.info("Getting Definitions Document..."); + + StringReader reader = new StringReader(xmlString); + + // unmarshall the XML string + Definitions jaxBDefinitions = (Definitions) createUnmarshaller().unmarshal(reader); + TServiceTemplate serviceTemplate = (TServiceTemplate) jaxBDefinitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().get(0); + + logger.info("Unmarshalling successful! "); + + return serviceTemplate.getTopologyTemplate(); + + } catch (JAXBException e) { + logger.error(e.getLocalizedMessage()); + } + return null; + } + + /** + * This method returns {@link TRelationshipTemplate}s as a JaxBObject. + * + * @param xmlString + * the {@link TRelationshipTemplate} to be unmarshalled + * + * @return the unmarshalled {@link TRelationshipTemplate} + */ + public static List<TRelationshipTemplate> getRelationshipTemplatesAsJaxBObject(String xmlString) { + try { + StringReader reader = new StringReader(xmlString); + + // unmarshall + Definitions jaxBDefinitions = (Definitions) createUnmarshaller().unmarshal(reader); + TServiceTemplate serviceTemplate = (TServiceTemplate) jaxBDefinitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().get(0); + + List<TRelationshipTemplate> foundRTs = new ArrayList<>(); + for (TEntityTemplate entity : serviceTemplate.getTopologyTemplate().getNodeTemplateOrRelationshipTemplate()) { + if (entity instanceof TRelationshipTemplate) { + foundRTs.add((TRelationshipTemplate) entity); + } + } + + return foundRTs; + + } catch (JAXBException e) { + logger.error(e.getLocalizedMessage()); + } + return null; + + } + + /** + * Turns XML Strings into {@link TEntityTemplate} objects using JaxB. + * + * @param xmlString + * the XMLString to be parsed + * @return the parsed XMLString as {@link TEntityTemplate} + */ + public static List<TEntityTemplate> getEntityTemplatesAsJaxBObject(String xmlString) { + try { + StringReader reader = new StringReader(xmlString); + + Definitions jaxBDefinitions = (Definitions) createUnmarshaller().unmarshal(reader); + TServiceTemplate serviceTemplate = (TServiceTemplate) jaxBDefinitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().get(0); + + return serviceTemplate.getTopologyTemplate().getNodeTemplateOrRelationshipTemplate(); + + } catch (JAXBException e) { + logger.error(e.getLocalizedMessage()); + } + return null; + + } + + /** + * Converts any object of the TOSCA data model to a JaxBObject. + * + * @param xmlString + * the {@link Definitions} object to be converted + * + * @return the unmarshalled {@link Definitions} object + */ + public static Definitions getXasJaxBObject(String xmlString) { + try { + StringReader reader = new StringReader(xmlString); + Definitions jaxBDefinitions = (Definitions) createUnmarshaller().unmarshal(reader); + + return jaxBDefinitions; + + } catch (JAXBException e) { + logger.error(e.getLocalizedMessage()); + } + return null; + + } + + /** + * This method adds a selection of {@link TNodeTemplate}- and {@link TRelationshipTemplate}-XML-Strings to a {@link TTopologyTemplate}-XML-String using JAXB. + * After the templates have been added, the {@link TTopologyTemplate} object is re-marshalled to an XML-String. + * + * This method is called by the selectionHandler.jsp after several Node or RelationshipTemplates have been chosen in a dialog. + * + * @param topology + * the topology as XML string + * @param allTemplateChoicesAsXML + * all possible template choices as TOSCA-XML strings containing the complete templates + * @param selectedNodeTemplatesAsJSON + * the names of the selected NodeTemplates as JSONArray + * @param selectedRelationshipTemplatesAsJSON + * the names of the selected RelationshipTemplates as JSONArray + * + * @return the complete topology XML string + */ + public static String addTemplatesToTopology(String topology, String allTemplateChoicesAsXML, String selectedNodeTemplatesAsJSON, String selectedRelationshipTemplatesAsJSON) { + try { + + // initialization code for the jackson types used to convert JSON string arrays to a java.util.List + ObjectMapper mapper = new ObjectMapper(); + TypeFactory factory = mapper.getTypeFactory(); + + // convert the JSON array containing the names of the selected RelationshipTemplates to a java.util.List + List<String> selectedRelationshipTemplates = mapper.readValue(selectedRelationshipTemplatesAsJSON, factory.constructCollectionType(List.class, String.class)); + + // convert the topology and the choices to objects using JAXB + TTopologyTemplate topologyTemplate = getTopologyAsJaxBObject(topology); + List<TEntityTemplate> allTemplateChoices = getEntityTemplatesAsJaxBObject(allTemplateChoicesAsXML); + + // this distinction of cases is necessary because it is possible that only RelationshipTemplates have been selected + if (selectedNodeTemplatesAsJSON != null) { + + // convert the JSON string array containing the names of the selected NodeTemplates to a java.util.List + List<String> selectedNodeTemplates = mapper.readValue(selectedNodeTemplatesAsJSON, factory.constructCollectionType(List.class, String.class)); + + // search the selected NodeTemplate in the List of all choices by its name to receive its object which will ne added to the topology + for (String nodeTemplateName : selectedNodeTemplates) { + for (TEntityTemplate choice : allTemplateChoices) { + if (choice instanceof TNodeTemplate) { + TNodeTemplate nodeTemplate = (TNodeTemplate) choice; + // matching a name is usually unsafe because the uniqueness cannot be assured, + // however similar names are not possible at this location due to the implementation of the selection dialogs + if (nodeTemplateName.equals(nodeTemplate.getName())) { + // add the selected NodeTemplate to the topology + topologyTemplate.getNodeTemplateOrRelationshipTemplate().add(nodeTemplate); + + // due to the mapping of IDs in the selection dialog, the corresponding Relationship Template of the inserted Node Template misses its SourceElement. + // Re-add it to avoid errors. + for (TEntityTemplate entity: topologyTemplate.getNodeTemplateOrRelationshipTemplate()) { + if (entity instanceof TRelationshipTemplate) { + TRelationshipTemplate relationshipTemplate = (TRelationshipTemplate) entity; + if (relationshipTemplate.getSourceElement().getRef() == null) { + // connect to the added NodeTemplate + SourceElement sourceElement = new SourceElement(); + sourceElement.setRef(nodeTemplate); + relationshipTemplate.setSourceElement(sourceElement); + } + } + } + } + } + } + } + + // now search and add the selected RelationshipTemplate object connecting to the inserted NodeTemplate + for (String relationshipTemplateName : selectedRelationshipTemplates) { + for (TEntityTemplate toBeAdded : allTemplateChoices) { + if (toBeAdded instanceof TRelationshipTemplate) { + TRelationshipTemplate relationshipTemplate = (TRelationshipTemplate) toBeAdded; + if (relationshipTemplateName.equals(relationshipTemplate.getName())) { + topologyTemplate.getNodeTemplateOrRelationshipTemplate().add(relationshipTemplate); + } + } + } + } + + } else { + // in this case only Relationship Templates have been selected + List<TRelationshipTemplate> allRelationshipTemplateChoices = JAXBHelper.getRelationshipTemplatesAsJaxBObject(allTemplateChoicesAsXML); + + // add the target Node Template to the topology which is unique due to the implementation of the selection dialog + topologyTemplate.getNodeTemplateOrRelationshipTemplate().add((TNodeTemplate) ((TRelationshipTemplate) allRelationshipTemplateChoices.get(0)).getTargetElement().getRef()); + + // search the JAXB object of the selected RelationshipTemplate and add it to the topology + for (String relationshipTemplateName : selectedRelationshipTemplates) { + for (TRelationshipTemplate choice : allRelationshipTemplateChoices) { + if (relationshipTemplateName.equals(choice.getName())) { + topologyTemplate.getNodeTemplateOrRelationshipTemplate().add(choice); + } + } + } + + for (TEntityTemplate entityTemplate : topologyTemplate.getNodeTemplateOrRelationshipTemplate()) { + if (entityTemplate instanceof TRelationshipTemplate) { + TRelationshipTemplate relationship = (TRelationshipTemplate) entityTemplate; + + // due to the mapping of IDs in the selection dialog, the corresponding Relationship Template of the inserted Node Template misses its SourceElement. + // Re-add it to avoid errors. + if (relationship.getSourceElement().getRef() == null) { + relationship.getSourceElement().setRef((TNodeTemplate) ((TRelationshipTemplate) allRelationshipTemplateChoices.get(0)).getTargetElement().getRef()); + } + } + } + } + + // re-convert the topology from a JAXB object to an XML string and return it + Definitions definitions = new Definitions(); + TServiceTemplate st = new TServiceTemplate(); + st.setTopologyTemplate(topologyTemplate); + definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().add(st); + JAXBContext context = JAXBContext.newInstance(Definitions.class); + Marshaller m = context.createMarshaller(); + StringWriter stringWriter = new StringWriter(); + + m.marshal(definitions, stringWriter); + + return stringWriter.toString(); + + } catch (JAXBException | IOException e) { + logger.error(e.getLocalizedMessage()); + } + + return null; + } + + + /** + * Marshalls a JAXB object of the TOSCA model to an XML string. + * + * @param clazz + * the class of the object + * @param obj + * the object to be marshalled + * + * @return + */ + public static String getXMLAsString(@SuppressWarnings("rawtypes") Class clazz, Object obj) { + try { + @SuppressWarnings("rawtypes") + JAXBElement rootElement = Util.getJAXBElement(clazz, obj); + JAXBContext context = JAXBContext.newInstance(TDefinitions.class); + Marshaller m; + + m = context.createMarshaller(); + + StringWriter w = new StringWriter(); + m.marshal(rootElement, w); + String res = w.toString(); + + return res; + } catch (JAXBException e) { + logger.error(e.getLocalizedMessage()); + } + return null; + } + + /** + * This methods alters the XML with JAXB so it can be imported in Winery. This is necessary because Winery needs additional information for the position of the templates in the + * Winery-Modeler-UI. + * + * This code is adapted from the org.eclipse.winery.repository.Utils.getXMLAsString() method. + * + * @param topology + * the {@link TTopologyTemplate} to be altered + * + * @return the altered {@link TTopologyTemplate} + */ + public static TTopologyTemplate buildXML(TTopologyTemplate topology) { + + // the coordinate of the NodeTemplate in Winery. Begin 100 pixel from the top to improve arrangement. + int yCoordinates = 100; + + for (TEntityTemplate template : topology.getNodeTemplateOrRelationshipTemplate()) { + // add node templates + if (template instanceof TNodeTemplate) { + + TNodeTemplate nodeTemplate = (TNodeTemplate) template; + + // remove the Requirements tag if necessary + if (nodeTemplate.getRequirements() != null && nodeTemplate.getRequirements().getRequirement() == null) { + nodeTemplate.setRequirements(null); + } + + ModelUtilities.setLeft(nodeTemplate, NODETEMPLATE_X_COORDINATE); + ModelUtilities.setTop(nodeTemplate, Integer.toString(yCoordinates)); + + yCoordinates += 150; + } + } + + return topology; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/NodeTemplateConnector.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/NodeTemplateConnector.java new file mode 100644 index 0000000..f1d4b6b --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/NodeTemplateConnector.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.helper; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.winery.model.tosca.TCapability; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TRelationshipType; +import org.eclipse.winery.model.tosca.TRequirement; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; + +/** + * This class searches a {@link TRelationshipType} which is able to connect two given {@link TNodeTemplate}s. + * + */ +public class NodeTemplateConnector { + + /** + * Searches a compatible {@link TRelationshipType} to connect two {@link TNodeTemplate}s. + * + * @param source + * the source {@link TNodeTemplate} + * @param target + * the target {@link TNodeTemplate} + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * @param requirement + * the {@link TRequirement} of the source {@link TNodeTemplate} + * + * @return a list of suitable {@link TRelationshipType}s + */ + public static List<TRelationshipType> findRelationshipType(TNodeTemplate source, TNodeTemplate target, TOSCAAnalyzer toscaAnalyzer, TRequirement requirement) { + + List<TRelationshipType> suitableRelationshipTypes = new ArrayList<TRelationshipType>(); + List<TRelationshipType> allRelationshipTypes = toscaAnalyzer.getRelationshipTypes(); + + // in case the connection to a placeholder is searched, no requirement exists + if (requirement != null) { + + List<TCapability> capabilities = target.getCapabilities().getCapability(); + + // check if a RelationshipType can connect a requirement of the source NodeTemplate to a capability of the target NodeTemplate + for (TRelationshipType relationshipType : allRelationshipTypes) { + if (relationshipType.getValidSource() != null && relationshipType.getValidTarget() != null) { + for (TCapability capability : capabilities) { + if ((relationshipType.getValidSource().getTypeRef().equals(requirement.getType()) && relationshipType.getValidTarget().getTypeRef().equals(capability.getType()))) { + suitableRelationshipTypes.add(relationshipType); + } + } + } + } + } + + // to extend the selection check if a RelationshipType can connect the type of the source NodeTemplate to the type of the target NodeTemplate + for (TRelationshipType rt : allRelationshipTypes) { + if (rt.getValidSource() != null && rt.getValidTarget() != null) { + if ((rt.getValidSource().getTypeRef().equals(source.getType()) && rt.getValidTarget().getTypeRef().equals(target.getType()))) { + suitableRelationshipTypes.add(rt); + } + } + } + + // in case no suitable relationship type could be found, search for generic types without the optional ValidSource / ValidTarget elements. + if (suitableRelationshipTypes.isEmpty()) { + for (TRelationshipType rt : allRelationshipTypes) { + if (rt.getValidSource() == null && rt.getValidTarget() == null) { + suitableRelationshipTypes.add(rt); + } + } + } + + return suitableRelationshipTypes; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/RESTHelper.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/RESTHelper.java new file mode 100644 index 0000000..789d529 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/RESTHelper.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.helper; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; + + +import org.eclipse.winery.common.Util; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.slf4j.LoggerFactory; + +/** + * This class contains helper methods to call the REST API and PUT/POST information to it. + */ +public class RESTHelper { + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(RESTHelper.class.getName()); + + /** + * This method uses a REST call to save the completed {@link TTopologyTemplate} to the repository. + * + * @param topology + * the {@link TTopologyTemplate} to be saved + * @param topologyTemplateURL + * the URL the {@link TTopologyTemplate} is saved to + * @param overwriteTopology + * whether the topology is overwritten or a new topology shall be created + * @param topologyName + * the name of the newly created topology to build the URL if a new topology shall be created + * @param topologyNamespace + * the name space of the newly created topology to build the URL if a new topology shall be created + * @param repositoryURL + * the URL to the repository to build the URL if a new topology shall be created + */ + public static void saveCompleteTopology(TTopologyTemplate topology, String topologyTemplateURL, boolean overwriteTopology, String topologyName, String topologyNamespace, String repositoryURL) { + try { + + URL url = null; + + if (overwriteTopology) { + url = new URL(topologyTemplateURL); + } else { + // this is necessary to avoid encoding issues + topologyNamespace = Util.DoubleURLencode(topologyNamespace); + // build the URL with the repositoryURL, the topology namespace and the topology name + url = new URL(repositoryURL + "/servicetemplates/" + topologyNamespace + "/" + topologyName + "/topologytemplate/"); + + logger.info("The URL the topology is saved to: " + url); + } + + // using SSL + System.setProperty("javax.net.ssl.trustStore", "jssecacerts.cert"); + + // configure message + HttpURLConnection urlConn; + urlConn = (HttpURLConnection) url.openConnection(); + + logger.info("Sending HTTP request..."); + + urlConn.setDoOutput(true); + urlConn.setRequestMethod("PUT"); + urlConn.setRequestProperty("Content-type", "text/xml"); + OutputStreamWriter out = new OutputStreamWriter(urlConn.getOutputStream()); + + // build the XML string to be saved + TTopologyTemplate outputTopology = JAXBHelper.buildXML(topology); + String outputString = JAXBHelper.getXMLAsString(outputTopology.getClass(), outputTopology); + + logger.info(outputString); + logger.info("Sending output to Winery."); + + out.write(outputString); + out.close(); + urlConn.getOutputStream().close(); + logger.info("Output sent, waiting for response..."); + urlConn.getInputStream(); + + logger.info("HTTP Response Code is: " + urlConn.getResponseCode()); + + } catch (IOException e) { + logger.error(e.getLocalizedMessage()); + } + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/Utils.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/Utils.java new file mode 100644 index 0000000..5f9826b --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/Utils.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.helper; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javax.xml.namespace.QName; + +import org.eclipse.winery.model.tosca.TCapabilityDefinition; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.model.tosca.TRequirement; +import org.eclipse.winery.model.tosca.TRequirementType; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; +import org.slf4j.LoggerFactory; + +/** + * Contains methods to match requirements and capabilities and find elements in the {@link TTopologyTemplate} + */ +public class Utils { + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Utils.class.getName()); + + /** + * This method searches {@link TNodeType}s in the repository that match a requirement. + * + * @param requirement + * the requirement to be matched + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return a list of the matched {@link TNodeType}s + */ + public static List<TNodeType> matchRequirementAndCapability(TRequirement requirement, TOSCAAnalyzer toscaAnalyzer) { + + List<TNodeType> possibleNodeTypes = new ArrayList<TNodeType>(); + + // find all matching Node Types for a requirement by the "requiredCapabilityType" attribute of its type + for (TRequirementType requirementType : toscaAnalyzer.getRequirementTypes()) { + if (requirementType.getName().equals(requirement.getType().getLocalPart())) { + + QName requiredCapabilityType = requirementType.getRequiredCapabilityType(); + for (TNodeType nodeType : toscaAnalyzer.getNodeTypes()) { + if (nodeType.getCapabilityDefinitions() != null) { + for (TCapabilityDefinition cd : nodeType.getCapabilityDefinitions().getCapabilityDefinition()) { + if (cd.getCapabilityType().getLocalPart().equals(requiredCapabilityType.getLocalPart())) { + possibleNodeTypes.add(nodeType); + } + } + } + } + } + } + + return possibleNodeTypes; + } + + /** + * Generates a random {@link UUID} exclusively used by the {@link TemplateBuilder}. + * + * @return the generated {@link UUID} id + */ + public static String createRandomID() { + return UUID.randomUUID().toString(); + } + + /** + * Returns a {@link TNodeTemplate} for a given Id. + * + * @param nodeTemplates + * all the {@link TNodeTemplate} in the {@link TTopologyTemplate} + * @param id + * the id of the {@link TNodeTemplate} to be found + * + * @return the found {@link TNodeTemplate} or null if not found + */ + public static TNodeTemplate getNodeTemplateForId(List<TNodeTemplate> nodeTemplates, String id) { + + for (TNodeTemplate nt : nodeTemplates) { + + if (nt.getId().equals(id)) { + return nt; + } + } + + logger.error("No NodeTemplate with " + id + " exists"); + + return null; + + } + + /** + * Returns a {@link TNodeType} for a given Id. + * + * @param nodeTypes + * All the {@link TNodeType} in the {@link TTopologyTemplate} + * @param Id + * The id of the {@link TNodeType} to be searched + * @return the {@link TNodeType} or null if not found + */ + public static TNodeType getNodeTypeForId(List<TNodeType> nodeTypes, QName id) { + + for (TNodeType nodeType : nodeTypes) { + if (nodeType.getName().equals(id.getLocalPart()) && nodeType.getTargetNamespace().equals(id.getNamespaceURI())) { + return nodeType; + } + } + + logger.error("No NodeType with " + id + " exists"); + + // no type could be found for the given ID, this case cannot occur if the topology was modelled in the Winery Topology Modeler + return null; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/package-info.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/package-info.java new file mode 100644 index 0000000..5d42210 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/helper/package-info.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +/** + * This package contains helper classes and methods to assist the completion. + */ +package org.eclipse.winery.topologymodeler.addons.topologycompleter.helper; + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/package-info.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/package-info.java new file mode 100644 index 0000000..19e334e --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/package-info.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +/** + * this package contains classes to complete a topology template + */ +package org.eclipse.winery.topologymodeler.addons.topologycompleter;
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/placeholderhandling/PlaceHolderHandler.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/placeholderhandling/PlaceHolderHandler.java new file mode 100644 index 0000000..a384e91 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/placeholderhandling/PlaceHolderHandler.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.placeholderhandling; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Constants; + +/** + * This class finds suitable replacement types for a place holder. + */ +public class PlaceHolderHandler { + + /** + * This method returns a suitable {@link TNodeType} to replace a given {@link TNodeTemplate} placeholder. + * A suitable Node Type to replace a placeholder is matched by its type. If the type of a NodeType equals the identifier of a placeholder + * it can be used to replace it. + * + * @param nodeTemplate + * the placeholder to be replaced + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return a list of {@link TNodeType}s to replace the placeholder + */ + public static List<TNodeType> getSuitableNodeTypes(TNodeTemplate nodeTemplate, TOSCAAnalyzer toscaAnalyzer) { + + List<TNodeType> suitableNodeTypes = new ArrayList<>(); + + // TODO: matching the name without a name space is unsafe and only works assuming that no one creates generic NodeTemplates with the same name as the place holders. + // However a NodeTemplate name does not have a name space. + if (nodeTemplate.getName().equals(Constants.PlaceHolders.WEBSERVER.toString())) { + for (TNodeType nodeType : toscaAnalyzer.getNodeTypes()) { + if (nodeType.getDerivedFrom() != null && nodeType.getDerivedFrom().getTypeRef().getLocalPart().equals(Constants.PlaceHolders.WEBSERVER.toString())) { + suitableNodeTypes.add(nodeType); + } + } + } else if (nodeTemplate.getName().equals(Constants.PlaceHolders.DATABASE.toString())) { + for (TNodeType nodeType : toscaAnalyzer.getNodeTypes()) { + if (nodeType.getDerivedFrom() != null && nodeType.getDerivedFrom().getTypeRef().getLocalPart().equals(Constants.PlaceHolders.DATABASE.toString())) { + suitableNodeTypes.add(nodeType); + } + } + } else if (nodeTemplate.getName().equals(Constants.PlaceHolders.OPERATINGSYSTEM.toString())) { + for (TNodeType nodeType : toscaAnalyzer.getNodeTypes()) { + if (nodeType.getDerivedFrom() != null && nodeType.getDerivedFrom().getTypeRef().getLocalPart().equals(Constants.PlaceHolders.OPERATINGSYSTEM.toString())) { + suitableNodeTypes.add(nodeType); + } + } + } else if (nodeTemplate.getName().equals(Constants.PlaceHolders.CLOUDPROVIDER.toString())) { + for (TNodeType nodeType : toscaAnalyzer.getNodeTypes()) { + if (nodeType.getDerivedFrom() != null && nodeType.getDerivedFrom().getTypeRef().getLocalPart().equals(Constants.PlaceHolders.CLOUDPROVIDER.toString())) { + suitableNodeTypes.add(nodeType); + } + } + } + + return suitableNodeTypes; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/CompletionInterface.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/CompletionInterface.java new file mode 100644 index 0000000..e46d3b1 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/CompletionInterface.java @@ -0,0 +1,257 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion; + +import java.util.List; +import java.util.Map; + +import org.eclipse.winery.model.tosca.TEntityTemplate; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRelationshipType; +import org.eclipse.winery.model.tosca.TRequirementType; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.repository.client.IWineryRepositoryClient; +import org.eclipse.winery.repository.client.WineryRepositoryClientFactory; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.DeferredAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.PlaceHolderAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.RequirementAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Constants; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.JAXBHelper; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.RESTHelper; +import org.slf4j.LoggerFactory; + +/** + * This class is the entry point of the TOSCA topology completion which is called by the Winery Topology Modeler. + * It receives an incomplete {@link TTopologyTemplate} from Winery. + * The completion of the incomplete {@link TTopologyTemplate} is managed by this class. + */ +public class CompletionInterface { + + /** + * Logger for debug reasons. + */ + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(CompletionInterface.class.getName()); + + /** + * This global variable is returned to the Winery Topology Modelerer via getCurrentTopology() to display intermediate results when an user interaction is necessary. + */ + private TTopologyTemplate currentTopology; + + /** + * This list contains {@link TTopologyTemplate}s to be chosen by the user when the topology solution isn't unique. + */ + private List<TTopologyTemplate> topologyTemplateChoices; + + /** + * This List contains {@link TRelationshipTemplate}s to be chosen by the user. + */ + private List<TEntityTemplate> relationshipTemplateChoices; + + /** + * This Map contains {@link TNodeTemplate}s and {@link TRelationshipTemplate}s to be chosen by the user during the step-by-step approach. + */ + private Map<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>> nodeTemplateChoices; + + /** + * String containing an error message to be displayed in Winery if necessary. + */ + private String errorMessage = ""; + + /** + * This method receives an incomplete {@link TTopologyTemplate} and the repository content from Winery. After analyzing the {@link TTopologyTemplate}, the topology is completed. This method will + * return a message after the completion whether the completion was successful, has failed or the user has to interact. + * + * @param topology + * (XMLString) the {@link TTopologyTemplate} to be completed as XMLString + * @param serviceTemplateName + * the name of the ServiceTemplate for REST calls + * @param topologyTemplateURL + * the URL where the template is saved to + * @param overwriteTopology + * determines in which way the {@link TTopologyTemplate} is saved. The current {@link TTopologyTemplate} can either be overwritten or a new topology can be created. + * @param topologyName + * the name of the {@link TTopologyTemplate} when a new {@link TTopologyTemplate} shall be created + * @param topologyNamespace + * the namespace of the {@link TTopologyTemplate} when a new {@link TTopologyTemplate} shall be created + * @param repositoryURL + * the URL to the repository to receive and write TOSCA specific information + * @param stepByStep + * whether the topology completion is processed step-by-step or not + * @param restarted + * whether the topology completion is restarted or started for the first time + * + * @return a message to Winery that contains information whether the topology is complete, the user has to interact or an error occurred. + */ + public String complete(String topology, String serviceTemplateName, String topologyTemplateURL, Boolean overwriteTopology, + String topologyName, String topologyNamespace, String repositoryURL, boolean stepByStep, boolean restarted) { + + logger.info("Starting completion..."); + + //////////////////////////////////////// + // STEP 1: Receive topology from Winery + //////////////////////////////////////// + + logger.info("Saving to: " + topologyTemplateURL); + + logger.info("The service template to be completed is: " + serviceTemplateName); + + // receive types from repository + IWineryRepositoryClient client = WineryRepositoryClientFactory.getWineryRepositoryClient(); + client.addRepository(repositoryURL); + + List<TNodeType> nodeTypeList = (List<TNodeType>) client.getAllTypes(TNodeType.class); + List<TRelationshipType> relationshipTypeList = (List<TRelationshipType>) client.getAllTypes(TRelationshipType.class); + List<TRequirementType> requirementTypeList = (List<TRequirementType>) client.getAllTypes(TRequirementType.class); + + ///////////////////////////////////// + // Step 2: Analyze topology content + ///////////////////////////////////// + + logger.info("The modelled topology as XML: " + topology); + + TTopologyTemplate topologyTemplate = JAXBHelper.getTopologyAsJaxBObject(topology); + + logger.info("Analyzing topology..."); + + // analyze the received topology + TOSCAAnalyzer toscaAnalyzer = new TOSCAAnalyzer(); + toscaAnalyzer.analyzeTOSCATopology(topologyTemplate); + toscaAnalyzer.setTypes(nodeTypeList, relationshipTypeList, requirementTypeList); + + // if the topology is already complete, a message is displayed + if (checkCompletnessOfTopology(toscaAnalyzer) && !restarted) { + return Constants.CompletionMessages.TOPOLOGYCOMPLETE.toString(); + } else { + + ///////////////////////////////////////// + // Step 3: Invoke the topology completion + ///////////////////////////////////////// + logger.info("Invoking Topology Completion..."); + + CompletionManager completionManager = new CompletionManager(toscaAnalyzer, stepByStep); + List<TTopologyTemplate> completedTopology = completionManager.manageCompletion(topologyTemplate); + + // the user has to interact by choosing a RelationshipTemplate, send message to Winery which will display a dialog + if (completionManager.getUserInteraction() && !stepByStep) { + currentTopology = completedTopology.get(0); + relationshipTemplateChoices = completionManager.getChoices(); + + return Constants.CompletionMessages.USERINTERACTION.toString(); + + } else if (completionManager.getNodeTemplateUserInteraction() && stepByStep) { + // the topology completion is processed Step-by-Step, the user has to choose Node and RelationshipTemplates to be inserted + currentTopology = completedTopology.get(0); + nodeTemplateChoices = completionManager.getTemplateChoices(); + + for (TNodeTemplate nodeTemplate : nodeTemplateChoices.keySet()) { + Map<TNodeTemplate, List<TEntityTemplate>> entityTemplates = nodeTemplateChoices.get(nodeTemplate); + + for (TNodeTemplate entity : entityTemplates.keySet()) { + for (TEntityTemplate relationshipTemplate : entityTemplates.get(entity)) { + // remove entity that has to be chosen next + if (currentTopology.getNodeTemplateOrRelationshipTemplate().contains(relationshipTemplate)) { + currentTopology.getNodeTemplateOrRelationshipTemplate().remove(relationshipTemplate); + } else if (currentTopology.getNodeTemplateOrRelationshipTemplate().contains(entity)) { + currentTopology.getNodeTemplateOrRelationshipTemplate().remove(entity); + } + } + } + } + + return Constants.CompletionMessages.STEPBYSTEP.toString(); + } + + logger.info("Completion successful!"); + + if (completedTopology.size() == 1) { + // solution is unique, save the topology + RESTHelper.saveCompleteTopology(completedTopology.get(0), topologyTemplateURL, overwriteTopology, topologyName, topologyNamespace, repositoryURL); + return Constants.CompletionMessages.SUCCESS.toString(); + } else if (completedTopology.size() > 1) { + // if there are several topology solutions, let the user choose + this.topologyTemplateChoices = completedTopology; + return Constants.CompletionMessages.USERTOPOLOGYSELECTION.toString(); + } else { + // an error occurred + errorMessage = "Error: No suitable NodeTemplate could be found for a Requirement or PlaceHolder."; + return Constants.CompletionMessages.FAILURE.toString(); + } + } + } + + /** + * This method checks if the topology is already complete. It will be called before executing the topology completion but + * only in case the topology completion isn't restarted after a user selection. + * + * @param toscaAnalyzer + * the topology to be checked + * @return whether the topology is complete or not + */ + public boolean checkCompletnessOfTopology(TOSCAAnalyzer toscaAnalyzer) { + + if (RequirementAnalyzer.analyzeRequirements(toscaAnalyzer).isEmpty() && PlaceHolderAnalyzer.analyzePlaceHolders(toscaAnalyzer).isEmpty() + && DeferredAnalyzer.analyzeDeferredRelations(toscaAnalyzer).isEmpty()) { + return true; + } else { + return false; + } + } + + /** + * Returns the current state of the completion. + * + * @return the current {@link TTopologyTemplate} + */ + public TTopologyTemplate getCurrentTopology() { + return currentTopology; + } + + /** + * Returns the choices whenever there are several possible complete {@link TTopologyTemplate}s. They will be displayed in Winery and chosen by the user. + * + * @return the possible {@link TTopologyTemplate} choices as a list. + */ + public List<TTopologyTemplate> getTopologyTemplateChoices() { + return topologyTemplateChoices; + } + + /** + * Returns the {@link TRelationshipTemplate} choices + * + * @return the {@link TRelationshipTemplate}s to be chosen + */ + public List<TEntityTemplate> getRelationshipTemplateChoices() { + return relationshipTemplateChoices; + } + + /** + * Returns several {@link TNodeTemplate} and {@link TRelationshipTemplate} choices when the user selected the step-by-step approach. + * + * @return the {@link TNodeTemplate} choices + */ + public Map<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>> getNodeTemplateChoices() { + return nodeTemplateChoices; + } + + /** + * Returns a message when an error occurred during the completion. + * + * @return the error message + */ + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/CompletionManager.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/CompletionManager.java new file mode 100644 index 0000000..1f47cf5 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/CompletionManager.java @@ -0,0 +1,358 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.eclipse.winery.model.tosca.TEntityTemplate; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRequirement; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.DeferredAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.PlaceHolderAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.RequirementAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer.DeferredCompleter; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer.PlaceHolderCompleter; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer.RequirementCompleter; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer.StepByStepCompleter; + +/** + * This class manages the completion of a TOSCA {@link TTopologyTemplate}. + */ +public class CompletionManager { + + private static final Logger logger = Logger.getLogger(CompletionManager.class.getName()); + + /** + * {@link TOSCAAnalyzer} object to access the JAXB data model + */ + TOSCAAnalyzer toscaAnalyzer; + + /** + * Map containing the topology solutions. + * + * The first parameter of the map is an index used to traverse the map easily. The second parameter of the solutions map + * is another map containing a possible topology solution and a boolean value that determines if the topology is complete. + * When all topologies of the solution map are complete, it will be returned to Winery. + */ + Map<Integer, Map<TTopologyTemplate, Boolean>> solutions; + + /** + * Whether a step-by-step or an one-step approach is conducted + */ + boolean stepByStep; + + /** + * Map containing {@link TNodeTemplate}s and {@link TRelationshipTemplate}s to be chosen by the user in the step-by-step approach. + */ + private Map<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>> templateChoices; + + /** + * Whether a user interaction for choosing inserted {@link TNodeTemplate}s and {@link TRelationshipTemplate}s is necessary or not. + */ + private boolean nodeTemplateUserInteraction = false; + + /** + * List containing {@link TRelationshipTemplate} to be chosen by the user. + */ + private List<TEntityTemplate> choices; + + /** + * Whether a user interaction for choosing inserted {@link TRelationshipTemplate}s is necessary or not. + */ + boolean userInteraction = false; + + /** + * The index of the topology solutions map. + */ + int index; + + /** + * The class constructor. + * + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * @param stepByStep + * whether the topology completion is processed step-by-step or not + */ + public CompletionManager(TOSCAAnalyzer toscaAnalyzer, boolean stepByStep) { + this.toscaAnalyzer = toscaAnalyzer; + this.stepByStep = stepByStep; + this.index = 0; + + // instantiate the solution map + solutions = new HashMap<Integer, Map<TTopologyTemplate, Boolean>>(); + } + + /** + * This recursive method analyzes and completes a TOSCA {@link TTopologyTemplate}. + * + * @param topology + * the TOSCA {@link TTopologyTemplate} to be completed + * + * @return the complete TOSCA {@link TTopologyTemplate} object + */ + public List<TTopologyTemplate> manageCompletion(TTopologyTemplate topology) { + + // ------------------------- + // Analyze topology template + // ------------------------- + + // the data model must be cleared before analyzing the topology + toscaAnalyzer.clear(); + + // analyze the content of the topology template + toscaAnalyzer.analyzeTOSCATopology(topology); + + // Note: The TOSCAAnalyzer object is used for the analysis to access the NodeTemplates or RelationshipTemplates directly, + // so no cast from the parent type TEntityTemplate is required + + // -------------------------------- + // Analyze unfulfilled requirements + // -------------------------------- + Map<TRequirement, TNodeTemplate> unfulfilledRequirements = RequirementAnalyzer.analyzeRequirements(toscaAnalyzer); + + // --------------------------------------- + // Analyze the occurrence of place holders + // --------------------------------------- + List<TNodeTemplate> placeHolders = PlaceHolderAnalyzer.analyzePlaceHolders(toscaAnalyzer); + + // -------------------------------------------------------- + // Analyze the occurrence of deferred RelationshipTemplates + // -------------------------------------------------------- + List<TRelationshipTemplate> deferredRelations = DeferredAnalyzer.analyzeDeferredRelations(toscaAnalyzer); + + // --------------------- + // Complete the topology + // --------------------- + + // with the step by step approach, a user interaction is always necessary. So the topology and + // the choices will be returned in every step. A combination of the step-by-step approach and a deferred topology is not + // possible because every path of the depth search can lead to a dead end. + if (stepByStep && deferredRelations.isEmpty()) { + logger.info("Completing topology step by step."); + + List<TTopologyTemplate> solutionList = new ArrayList<TTopologyTemplate>(); + + if (!unfulfilledRequirements.isEmpty() && placeHolders.isEmpty()) { + + // complete a topology containing requirements step by step using the StepByStepCompleter class + StepByStepCompleter stepByStepCompleter = new StepByStepCompleter(topology); + stepByStepCompleter.completeTopologyStepByStep(unfulfilledRequirements, toscaAnalyzer); + + // get the NodeTemplate choices for the user + templateChoices = stepByStepCompleter.getTemplateChoices(); + nodeTemplateUserInteraction = true; + + solutionList.add(topology); + + logger.info("Returning topology for user interaction"); + + return solutionList; + + } else if (unfulfilledRequirements.isEmpty() && !placeHolders.isEmpty()) { + + // complete a topology containing place holders step by step using the StepByStepCompleter class + StepByStepCompleter stepByStepCompleter = new StepByStepCompleter(topology); + TRelationshipTemplate genericRelationship = stepByStepCompleter.completeWildcardTopologyStepByStep(placeHolders, toscaAnalyzer); + + // get the NodeTemplate selection for the user to choose + templateChoices = stepByStepCompleter.getTemplateChoices(); + nodeTemplateUserInteraction = true; + + TNodeTemplate toBeRemoved = stepByStepCompleter.getPlaceHolder(); + topology.getNodeTemplateOrRelationshipTemplate().remove(toBeRemoved); + topology.getNodeTemplateOrRelationshipTemplate().remove(genericRelationship); + + solutionList.add(topology); + logger.info("Returning topology for user interaction"); + + return solutionList; + } else if (unfulfilledRequirements.isEmpty() && placeHolders.isEmpty() && deferredRelations.isEmpty()) { + + // the topology is complete, return it to Winery + + logger.info("The topology is complete."); + + nodeTemplateUserInteraction = false; + solutionList.add(topology); + return solutionList; + } + } else { + // the one-step approach is chosen or the topology contains deferred-RelationshipTemplates + if (unfulfilledRequirements.isEmpty() && placeHolders.isEmpty() && deferredRelations.isEmpty()) { + // the topology does not contain any elements that have to completed, it can be defined as complete + if (solutions.isEmpty()) { + // no topology solutions found, topology could not be completed due to missing types. + // Return an empty list, an error message will be shown in Winery. + return new ArrayList<TTopologyTemplate>(); + } else { + // this topology is complete, set its boolean value in the map to true + for (Integer i : solutions.keySet()) { + for (TTopologyTemplate t : solutions.get(i).keySet()) { + if (t.equals(topology)) { + solutions.get(i).put(topology, true); + } + } + } + + // check if the map still contains any incomplete topologies. If this is the case, the recursion will continue. + // Otherwise the solutions map is returned. + if (!solutions.values().contains(false)) { + logger.info("The topology is complete."); + List<TTopologyTemplate> sol = new ArrayList<TTopologyTemplate>(); + + for (Integer i : solutions.keySet()) { + sol.addAll(solutions.get(i).keySet()); + } + return sol; + } + } + } else if (!unfulfilledRequirements.isEmpty() && placeHolders.isEmpty() && deferredRelations.isEmpty()) { + + logger.info("The topology contains Requirements, but no Place Holders."); + + // complete a topology containing Requirements in one step using the RequirementCompleter class + RequirementCompleter requirementCompleter = new RequirementCompleter(topology); + + List<TTopologyTemplate> completeTopology = requirementCompleter.completeRequirementTopology(unfulfilledRequirements, toscaAnalyzer); + + for (TTopologyTemplate topologySolution : completeTopology) { + Map<TTopologyTemplate, Boolean> topologyMap = new HashMap<TTopologyTemplate, Boolean>(); + topologyMap.put(topologySolution, false); + solutions.put(index, topologyMap); + } + + // complete all topology solutions recursively + for (TTopologyTemplate topologySolution : completeTopology) { + manageCompletion(topologySolution); + index++; + } + + } else if (unfulfilledRequirements.isEmpty() && !placeHolders.isEmpty() || !unfulfilledRequirements.isEmpty() && !placeHolders.isEmpty()) { + + logger.info("The topology contains one or more PlaceHolders."); + + // complete a topology containing place holders in one step using the PlaceHolderCompleter class + PlaceHolderCompleter placeHolderCompleter = new PlaceHolderCompleter(topology); + + List<TTopologyTemplate> completeTopology = placeHolderCompleter.completePlaceholderTopology(placeHolders, toscaAnalyzer); + + if (placeHolderCompleter.getUserInteraction()) { + choices = placeHolderCompleter.getChoices(); + userInteraction = true; + + // user interaction is necessary to choose a inserted Relationship Template, return the topology to winery + List<TTopologyTemplate> intermediateSolutions = new ArrayList<>(); + TRelationshipTemplate toBeRemoved = null; + for (TEntityTemplate entityTemplate : topology.getNodeTemplateOrRelationshipTemplate()) { + if (entityTemplate instanceof TRelationshipTemplate) { + TRelationshipTemplate relationshipTemplate = (TRelationshipTemplate) entityTemplate; + if (relationshipTemplate.getTargetElement().getRef().equals(placeHolderCompleter.getPlaceHolder())) { + toBeRemoved = relationshipTemplate; + } + } + } + + topology.getNodeTemplateOrRelationshipTemplate().remove(toBeRemoved); + topology.getNodeTemplateOrRelationshipTemplate().remove(placeHolderCompleter.getPlaceHolder()); + + intermediateSolutions.add(topology); + return intermediateSolutions; + } + + int i = 0; + + for (TTopologyTemplate topologySolution : completeTopology) { + Map<TTopologyTemplate, Boolean> topologyMap = new HashMap<TTopologyTemplate, Boolean>(); + topologyMap.put(topologySolution, false); + solutions.put(i, topologyMap); + i++; + } + + for (TTopologyTemplate topologySolution : completeTopology) { + manageCompletion(topologySolution); + } + } else if (!deferredRelations.isEmpty()) { + + logger.info("The topology contains deferred RelationshipTemplates."); + + // complete a topology containing deferred Relationship Templates in one step using the DeferredCompleter class + DeferredCompleter deferredCompleter = new DeferredCompleter(topology); + List<TTopologyTemplate> completeTopology = deferredCompleter.completeDeferredTopology(deferredRelations.get(0), toscaAnalyzer); + + int i = 0; + for (TTopologyTemplate solutionTemplate : completeTopology) { + Map<TTopologyTemplate, Boolean> topologyMap = new HashMap<TTopologyTemplate, Boolean>(); + topologyMap.put(solutionTemplate, false); + solutions.put(i, topologyMap); + i++; + } + + for (TTopologyTemplate topologySolution : completeTopology) { + manageCompletion(topologySolution); + } + } + List<TTopologyTemplate> sol = new ArrayList<TTopologyTemplate>(); + for (Integer i : solutions.keySet()) { + sol.addAll(solutions.get(i).keySet()); + } + return sol; + } + + return new ArrayList<TTopologyTemplate>(); + } + + /** + * Returns whether an user interaction is necessary or not + * + * @return the field userInteraction + */ + public boolean getUserInteraction() { + return userInteraction; + } + + /** + * The possible {@link TRelationshipTemplate} choices + * + * @return the field choices + */ + public List<TEntityTemplate> getChoices() { + return choices; + } + + /** + * A map of {@link TNodeTemplate}s and {@link TRelationshipTemplate}s when completing a topology step by step + * + * @return the field nodeTemplateChoices + */ + public Map<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>> getTemplateChoices() { + return templateChoices; + } + + /** + * Returns whether user interaction by choosing {@link TNodeTemplate}s and {@link TRelationshipTemplate}s is necessary or not + * + * @return the field nodeTemplateUserInteraction + */ + public boolean getNodeTemplateUserInteraction() { + return nodeTemplateUserInteraction; + } + +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/DeferredCompleter.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/DeferredCompleter.java new file mode 100644 index 0000000..8bdf01e --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/DeferredCompleter.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.winery.common.ModelUtilities; +import org.eclipse.winery.model.tosca.TCapability; +import org.eclipse.winery.model.tosca.TEntityTemplate; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRelationshipType; +import org.eclipse.winery.model.tosca.TRequirement; +import org.eclipse.winery.model.tosca.TRequirementType; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.NodeTemplateConnector; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Utils; + +/** + * This class serves the completion of a topology containing Deferred {@link TRelationshipTemplate}s. + */ +public class DeferredCompleter { + + /** + * The TOSCA {@link TTopologyTemplate} document + */ + TTopologyTemplate topology; + + /** + * A Map containing the requirements removed during the algorithm and their corresponding {@link TNodeTemplate}. + */ + Map<TRequirement, TNodeTemplate> removedRequirements; + + /** + * Constructor of the class. + * + * @param topology + * the {@link TTopologyTemplate} to be completed + */ + public DeferredCompleter(TTopologyTemplate topology) { + this.topology = topology; + removedRequirements = new HashMap<TRequirement, TNodeTemplate>(); + } + + /** + * Completes a {@link TTopologyTemplate} that contains deferred {@link TRelationshipTemplate}s with a depth search algorithm. A deferred {@link TRelationshipTemplate} serves as place holder for a + * number of {@link TNodeTemplate}s and {@link TRelationshipTemplate}s. + * + * @param deferredRelation + * all found deferred {@link TRelationshipTemplate}s in the topology + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return the completed topology + */ + public List<TTopologyTemplate> completeDeferredTopology(TRelationshipTemplate deferredRelation, TOSCAAnalyzer toscaAnalyzer) { + + List<TTopologyTemplate> solutions = new ArrayList<TTopologyTemplate>(); + + TNodeTemplate source = (TNodeTemplate) deferredRelation.getSourceElement().getRef(); + TNodeTemplate target = (TNodeTemplate) deferredRelation.getTargetElement().getRef(); + + // TODO Remove this "if clause" after the Provisioning-API is implemented. At the moment Deferred RelationshipTemplates can't be completed + // without the existence of Requirements + if (source.getRequirements() != null && !source.getRequirements().getRequirement().isEmpty()) { + topology.getNodeTemplateOrRelationshipTemplate().remove(deferredRelation); + runDepthFirstSearch(source, target, new ArrayList<TEntityTemplate>(), solutions, toscaAnalyzer); + } + + /** + * Note: This code adds Requirements to NodeTemplates that has been removed during the algorithm but could not + * be used to replace the Deferred-RelationshipTemplates. If this step is not done, requirements could get lost. + * + * Therefore all removed Requirements are checked for fulfillment in the topology. If they have not been fulfilled + * they are re-added to the topology. + */ + Set<TRequirement> keySet = removedRequirements.keySet(); + + for (TTopologyTemplate topologyTemplate: solutions) { + boolean fulfilled = false; + for (TRequirement requirement: keySet) { + for (TEntityTemplate entity: topologyTemplate.getNodeTemplateOrRelationshipTemplate()) { + if (entity instanceof TNodeTemplate) { + TNodeTemplate nodeTemplate = (TNodeTemplate) entity; + if (nodeTemplate.getCapabilities() != null) { + for (TCapability capability: nodeTemplate.getCapabilities().getCapability()) { + String reqCapaType = ""; + for (TRequirementType reqType: toscaAnalyzer.getRequirementTypes()) { + if (reqType.getName().equals(requirement.getType().getLocalPart())) { + reqCapaType = reqType.getRequiredCapabilityType().getLocalPart(); + } + } + if (capability.getName().equals(reqCapaType)) { + fulfilled = true; + } + } + } + } + } + if (!fulfilled) { + for (TEntityTemplate entity: topologyTemplate.getNodeTemplateOrRelationshipTemplate()) { + if (entity.equals(removedRequirements.get(requirement))) { + TNodeTemplate foundNT = (TNodeTemplate) entity; + foundNT.getRequirements().getRequirement().add(requirement); + } + } + } + } + } + + return solutions; + } + + /** + * Runs a recursive depth search to find the path to the target NodeTemplate. + * + * @param source + * the source node of a given {@link TRelationshipTemplate} + * @param target + * the target node of a given {@link TRelationshipTemplate} + * @param path + * the current path to the target (can be incomplete) + * @param solutions + * list containing all possible solutions of the completion + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return the path to the target NodeTemplate + */ + private void runDepthFirstSearch(TNodeTemplate source, TNodeTemplate target, List<TEntityTemplate> path, List<TTopologyTemplate> solutions, TOSCAAnalyzer toscaAnalyzer) { + + List<TNodeType> matchingNodeTypes = new ArrayList<TNodeType>(); + + if (source.getRequirements() != null) { + + List<TRequirement> requirementsOfTemplate = new ArrayList<>(); + for (TRequirement requirement : source.getRequirements().getRequirement()) { + requirementsOfTemplate.add(requirement); + } + + for (TRequirement requirement : requirementsOfTemplate) { + + // save the requirement to a list to avoid losing requirements (see line 83) + TRequirement sourceRequirement = new TRequirement(); + sourceRequirement.setId(requirement.getId()); + sourceRequirement.setName(requirement.getName()); + sourceRequirement.setType(requirement.getType()); + + // Remember the removed requirements. In case a requirement + // can't be used for completing the deferred RelationshipTemplate it has to be re-added to the topology. + removedRequirements.put(sourceRequirement, source); + + // search for matching NodeTypes for the requirement + matchingNodeTypes.addAll(Utils.matchRequirementAndCapability(requirement, toscaAnalyzer)); + + // remove the requirement so it is not handled again during the algorithm + source.getRequirements().getRequirement().remove(requirement); + } + } + for (TNodeType match : matchingNodeTypes) { + + if (match.getName().equals(target.getType().getLocalPart()) && match.getTargetNamespace().equals(target.getType().getNamespaceURI())) { + // the search was successful connect the target + List<TRelationshipType> suitableRTs = NodeTemplateConnector.findRelationshipType(source, target, toscaAnalyzer, null); + + for (TRelationshipType rt : suitableRTs) { + TRelationshipTemplate relationship = ModelUtilities.instantiateRelationshipTemplate(rt, source, target); + path.add(relationship); + } + + TTopologyTemplate possiblePath = new TTopologyTemplate(); + possiblePath.getNodeTemplateOrRelationshipTemplate().addAll(topology.getNodeTemplateOrRelationshipTemplate()); + + // add the path to the topology + for (TEntityTemplate pathTemplate : path) { + possiblePath.getNodeTemplateOrRelationshipTemplate().add(pathTemplate); + } + + possiblePath.getNodeTemplateOrRelationshipTemplate().remove(target); + + // this is no good style, however the target has to be the last item in the list for a proper stack layouting + possiblePath.getNodeTemplateOrRelationshipTemplate().add(target); + solutions.add(possiblePath); + path.clear(); + } else { + + // the end of the path is not reached, add the found NodeTemplate and continue the depth search + TNodeTemplate instantiatedNodeTemplate = ModelUtilities.instantiateNodeTemplate(match); + + List<TRelationshipType> suitableRTs = NodeTemplateConnector.findRelationshipType(source, instantiatedNodeTemplate, toscaAnalyzer, null); + + for (TRelationshipType rt : suitableRTs) { + TRelationshipTemplate relationship = ModelUtilities.instantiateRelationshipTemplate(rt, source, instantiatedNodeTemplate); + path.add(relationship); + } + path.add(instantiatedNodeTemplate); + runDepthFirstSearch(instantiatedNodeTemplate, target, path, solutions, toscaAnalyzer); + + } + } + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/PlaceHolderCompleter.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/PlaceHolderCompleter.java new file mode 100644 index 0000000..e76d897 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/PlaceHolderCompleter.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.winery.common.ModelUtilities; +import org.eclipse.winery.model.tosca.TEntityTemplate; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRelationshipType; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.NodeTemplateConnector; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.placeholderhandling.PlaceHolderHandler; + +/** + * This class completes a {@link TTopologyTemplate} containing place holders. + */ +public class PlaceHolderCompleter { + + /** + * The {@link TTopologyTemplate} to be completed + */ + TTopologyTemplate topology; + + /** + * List containing user choices for {@link TRelationshipTemplate}s + */ + List<TEntityTemplate> choices; + + /** + * Whether an user interaction is necessary or not + */ + boolean userInteraction; + + /** + * The last inserted place holder + */ + TNodeTemplate placeHolder; + + /** + * The constructor of the class PlaceHolderCompleter. + * + * @param topology + * the {@link TTopologyTemplate} to be completed + */ + public PlaceHolderCompleter(TTopologyTemplate topology) { + this.topology = topology; + userInteraction = false; + } + + /** + * This method completes a {@link TTopologyTemplate} containing place holders. + * + * @param placeHolders + * the contained place holders + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return the complete {@link TTopologyTemplate} + */ + public List<TTopologyTemplate> completePlaceholderTopology(List<TNodeTemplate> placeHolders, TOSCAAnalyzer toscaAnalyzer) { + + List<TTopologyTemplate> solutions = new ArrayList<TTopologyTemplate>(); + + for (TNodeTemplate placeHolder : placeHolders) { + + List<TNodeType> suitableNodeTypes = PlaceHolderHandler.getSuitableNodeTypes(placeHolder, toscaAnalyzer); + + // if there are more than one solution for an inserted NodeTemplate, + // create copies of the topology. The user can choose from them after the completion. + TTopologyTemplate topologyCopy = null; + + for (TNodeType suitableNodeType : suitableNodeTypes) { + topologyCopy = new TTopologyTemplate(); + topologyCopy.getNodeTemplateOrRelationshipTemplate().addAll(topology.getNodeTemplateOrRelationshipTemplate()); + + TNodeTemplate nodeTemplate = ModelUtilities.instantiateNodeTemplate(suitableNodeType); + + List<TNodeTemplate> sourceTemplates = new ArrayList<>(); + + // contains RelationshipTemplates connecting to a place holder. + // These Templates are generic and have to be replaced with + // concrete ones. + List<TRelationshipTemplate> placeholderConnections = new ArrayList<>(); + + TRelationshipTemplate foundTarget = null; + for (TEntityTemplate entity : topology.getNodeTemplateOrRelationshipTemplate()) { + if (entity instanceof TRelationshipTemplate) { + TRelationshipTemplate rt = (TRelationshipTemplate) entity; + if (((TNodeTemplate) rt.getTargetElement().getRef()).getId().equals(placeHolder.getId())) { + TRelationshipTemplate placeHolderConnection = (TRelationshipTemplate) entity; + placeholderConnections.add(placeHolderConnection); + sourceTemplates.add((TNodeTemplate) placeHolderConnection.getSourceElement().getRef()); + } else if (((TNodeTemplate) rt.getSourceElement().getRef()).getId().equals(placeHolder.getId())) { + foundTarget = (TRelationshipTemplate) entity; + } + } + } + + // collect all possible RelationshipTemplates that can be used to connect to the placeholder + choices = new ArrayList<>(); + + for (TNodeTemplate sourceTemplate : sourceTemplates) { + // find matching RelationshipTypes to connect the Node Templates + List<TRelationshipType> suitableRTs = NodeTemplateConnector.findRelationshipType(sourceTemplate, nodeTemplate, toscaAnalyzer, null); + for (TRelationshipType rt : suitableRTs) { + TRelationshipTemplate relationship = ModelUtilities.instantiateRelationshipTemplate(rt, sourceTemplate, nodeTemplate); + choices.add(relationship); + } + } + + // set the source elements of the RelationshipTemplates connecting from the replaced placeholder to other NodeTemplates + for (TEntityTemplate entityTemplate : topologyCopy.getNodeTemplateOrRelationshipTemplate()) { + if (entityTemplate instanceof TRelationshipTemplate) { + TRelationshipTemplate relationshipTemplate = (TRelationshipTemplate) entityTemplate; + if (relationshipTemplate.equals(foundTarget)) { + foundTarget.getSourceElement().setRef(nodeTemplate); + } + } + } + + // remove the generic connections to the place holder + topologyCopy.getNodeTemplateOrRelationshipTemplate().removeAll(placeholderConnections); + + // there are more than one possible Relationship Templates to connect to the inserted NodeTemplate(s), so + // interrupt the completion and ask the user which one to insert + if (choices.size() > 1 && sourceTemplates.size() == 1) { + + choices.add(sourceTemplates.get(0)); + choices.add(nodeTemplate); + topologyCopy.getNodeTemplateOrRelationshipTemplate().remove(placeHolder); + + userInteraction = true; + this.placeHolder = placeHolder; + break; + } else if (choices.size() == 1 || sourceTemplates.size() > 1) { + // replace the place holder with an actual NodeTemplate + topologyCopy.getNodeTemplateOrRelationshipTemplate().addAll(choices); + topologyCopy.getNodeTemplateOrRelationshipTemplate().add(nodeTemplate); + topologyCopy.getNodeTemplateOrRelationshipTemplate().remove(placeHolder); + } + solutions.add(topologyCopy); + } + } + return solutions; + } + + /** + * Returns the replaced place holder. + * + * @return the place holder + */ + public TNodeTemplate getPlaceHolder() { + return placeHolder; + } + + /** + * Returns whether an user interaction is necessary or not. + * + * @return the field userInteraction + */ + public boolean getUserInteraction() { + return userInteraction; + } + + /** + * Possible Relationship Template choices. + * + * @return the field choices + */ + public List<TEntityTemplate> getChoices() { + return choices; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/RequirementCompleter.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/RequirementCompleter.java new file mode 100644 index 0000000..b2e7c75 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/RequirementCompleter.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.winery.common.ModelUtilities; +import org.eclipse.winery.model.tosca.TEntityTemplate; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRelationshipType; +import org.eclipse.winery.model.tosca.TRequirement; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.NodeTemplateConnector; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Utils; + +public class RequirementCompleter { + + /** + * The TOSCA {@link TTopologyTemplate} document. + */ + TTopologyTemplate topology; + + /** + * The constructor the class. + * + * @param topology + * the topology to be completed + */ + public RequirementCompleter(TTopologyTemplate topology) { + this.topology = topology; + } + + /** + * This method completes a topology containing {@link TRequirement}s in one step (without user interaction). + * + * @param unfulfilledRequirements + * all the unfulfilled requirements that has been found in the topology + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + * + * @return the complete topology + */ + public List<TTopologyTemplate> completeRequirementTopology(Map<TRequirement, TNodeTemplate> unfulfilledRequirements, TOSCAAnalyzer toscaAnalyzer) { + + List<TTopologyTemplate> solutions = new ArrayList<TTopologyTemplate>(); + + Set<TRequirement> requirements = unfulfilledRequirements.keySet(); + + TNodeTemplate instantiatedNodeTemplate = null; + + // fulfill the Requirements + for (TRequirement requirement : requirements) { + + // remove the requirement from the NodeTemplate + TNodeTemplate requirementTemplate = unfulfilledRequirements.get(requirement); + for (TEntityTemplate element : topology.getNodeTemplateOrRelationshipTemplate()) { + if (requirementTemplate.getId().equals(element.getId())) { + ((TNodeTemplate) element).getRequirements().getRequirement().remove(requirement); + } + } + + List<TNodeType> possibleNodeTypes = Utils.matchRequirementAndCapability(requirement, toscaAnalyzer); + + // create a NodeTemplate for every matching Type, insert it into the topology and create a topology copy for each possible inserted NodeTemplate + TTopologyTemplate topologyCopy = null; + for (TNodeType possibleType : possibleNodeTypes) { + + topologyCopy = new TTopologyTemplate(); + topologyCopy.getNodeTemplateOrRelationshipTemplate().addAll(topology.getNodeTemplateOrRelationshipTemplate()); + + // instantiate the template + instantiatedNodeTemplate = ModelUtilities.instantiateNodeTemplate(possibleType); + topologyCopy.getNodeTemplateOrRelationshipTemplate().add(instantiatedNodeTemplate); + + TNodeTemplate correspondingNodeTemplate = unfulfilledRequirements.get(requirement); + + // find matching RelationshipTypes, instantiate RelationshipTemplates and connect the Node Templates + List<TRelationshipType> suitableRTs = NodeTemplateConnector.findRelationshipType(correspondingNodeTemplate, instantiatedNodeTemplate, toscaAnalyzer, requirement); + for (TRelationshipType rt : suitableRTs) { + TRelationshipTemplate relationship = ModelUtilities.instantiateRelationshipTemplate(rt, correspondingNodeTemplate, instantiatedNodeTemplate); + topologyCopy.getNodeTemplateOrRelationshipTemplate().add(relationship); + } + + solutions.add(topologyCopy); + } + if (solutions.size() > 1) { + break; + } + } + return solutions; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/StepByStepCompleter.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/StepByStepCompleter.java new file mode 100644 index 0000000..b8cc745 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/StepByStepCompleter.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.winery.common.ModelUtilities; +import org.eclipse.winery.model.tosca.TEntityTemplate; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TNodeType; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TRelationshipType; +import org.eclipse.winery.model.tosca.TRequirement; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.analyzer.TOSCAAnalyzer; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.NodeTemplateConnector; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.Utils; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.placeholderhandling.PlaceHolderHandler; + +/** + * This class handles topologies that are completed step by step + */ +public class StepByStepCompleter { + + /** + * the topology to be completed + */ + TTopologyTemplate topology; + + /** + * the Node and RelationshipTemplates chosen by the user in every step + */ + Map<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>> templateChoices; + + /** + * the last inserted place holder to be deleted + */ + private TNodeTemplate placeHolder; + + /** + * The constructor of the class. + * + * @param topology + * the {@link TTopologyTemplate} to be completed + */ + public StepByStepCompleter(TTopologyTemplate topology) { + this.topology = topology; + } + + /** + * This method is called when a topology containing {@link TRequirement}s is completed step by step. + * + * @param unfulfilledRequirements + * a list of unfulfilled requirements + * @param placeHolders + * a list of place holders to be fulfilled + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model + */ + public void completeTopologyStepByStep(Map<TRequirement, TNodeTemplate> unfulfilledRequirements, TOSCAAnalyzer toscaAnalyzer) { + + Set<TRequirement> requirements = unfulfilledRequirements.keySet(); + + TNodeTemplate nodeTemplate = null; + + for (TRequirement requirement : requirements) { + // remove the requirement from the NodeTemplate + TNodeTemplate requirementTemplate = unfulfilledRequirements.get(requirement); + for (TEntityTemplate element : topology.getNodeTemplateOrRelationshipTemplate()) { + if (requirementTemplate.getId().equals(element.getId())) { + ((TNodeTemplate) element).getRequirements().getRequirement().remove(requirement); + } + } + + List<TNodeType> possibleNodeTypes = Utils.matchRequirementAndCapability(requirement, toscaAnalyzer); + + // create a NodeTemplate for each matching node type + List<TNodeTemplate> possibleTemplates = new ArrayList<>(); + for (TNodeType possibleType : possibleNodeTypes) { + nodeTemplate = ModelUtilities.instantiateNodeTemplate(possibleType); + possibleTemplates.add(nodeTemplate); + } + + TNodeTemplate correspondingNodeTemplate = unfulfilledRequirements.get(requirement); + + Map<TNodeTemplate, List<TEntityTemplate>> entityTemplates = new HashMap<>(); + + // add all possible choices to a list and return it to the user + for (TNodeTemplate possibleTemplate : possibleTemplates) { + List<TEntityTemplate> choices = new ArrayList<TEntityTemplate>(); + List<TRelationshipType> suitableRTs = NodeTemplateConnector.findRelationshipType(correspondingNodeTemplate, possibleTemplate, toscaAnalyzer, requirement); + for (TRelationshipType rt : suitableRTs) { + TRelationshipTemplate relationship = ModelUtilities.instantiateRelationshipTemplate(rt, correspondingNodeTemplate, possibleTemplate); + choices.add(relationship); + } + entityTemplates.put(possibleTemplate, choices); + } + + templateChoices = new HashMap<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>>(); + templateChoices.put(correspondingNodeTemplate, entityTemplates); + + // let the user decide which template shall be inserted + break; + } + + } + + /** + * Completes a place holder {@link TTopologyTemplate} step by step. + * + * @param placeHolders + * the place holders of the topology + * @param toscaAnalyzer + * the {@link TOSCAAnalyzer} object to access the data model. + * @return the generic {@link TRelationshipTemplate} which connects to the place holder. + */ + public TRelationshipTemplate completeWildcardTopologyStepByStep(List<TNodeTemplate> placeHolders, TOSCAAnalyzer toscaAnalyzer) { + + // take the first place holder, the order doesn't matter in the step by step approach + TNodeTemplate placeHolder = placeHolders.get(0); + + // get suitable NodeTypes for a placeholder and instantiate NodeTemplates + List<TNodeType> suitableNodeTypes = PlaceHolderHandler.getSuitableNodeTypes(placeHolder, toscaAnalyzer); + List<TNodeTemplate> suitableNodeTemplates = new ArrayList<TNodeTemplate>(); + for (TNodeType suitableNodeType : suitableNodeTypes) { + TNodeTemplate nodeTemplate = ModelUtilities.instantiateNodeTemplate(suitableNodeType); + suitableNodeTemplates.add(nodeTemplate); + } + + /** + * map containing the choices for the user selection + */ + Map<TNodeTemplate, List<TEntityTemplate>> entityTemplates = new HashMap<>(); + + TNodeTemplate sourceTemplate = null; + + // the RelationshipTemplate connecting to the placeholder + TRelationshipTemplate connectingRelationshipTemplate = null; + + for (TEntityTemplate entity : topology.getNodeTemplateOrRelationshipTemplate()) { + if (entity instanceof TRelationshipTemplate) { + TRelationshipTemplate rt = (TRelationshipTemplate) entity; + if (((TNodeTemplate) rt.getTargetElement().getRef()).getId().equals(placeHolder.getId())) { + connectingRelationshipTemplate = (TRelationshipTemplate) entity; + sourceTemplate = (TNodeTemplate) connectingRelationshipTemplate.getSourceElement().getRef(); + } + } + } + + for (TNodeTemplate nodeTemplate : suitableNodeTemplates) { + + List<TEntityTemplate> choices = new ArrayList<>(); + + // find matching RelationshipTypes to connect the Node Templates + List<TRelationshipType> suitableRTs = NodeTemplateConnector.findRelationshipType(sourceTemplate, nodeTemplate, toscaAnalyzer, null); + for (TRelationshipType rt : suitableRTs) { + TRelationshipTemplate relationship = ModelUtilities.instantiateRelationshipTemplate(rt, sourceTemplate, nodeTemplate); + choices.add(relationship); + } + entityTemplates.put(nodeTemplate, choices); + } + + templateChoices = new HashMap<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>>(); + templateChoices.put(sourceTemplate, entityTemplates); + + this.placeHolder = placeHolder; + + return connectingRelationshipTemplate; + + } + + /** + * Returns a map containing the choices for the user selection when the topology is completed step by step. + * + * @return the field ntChoices + */ + public Map<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>> getTemplateChoices() { + return templateChoices; + } + + /** + * Returns the replaced place holder to remove it from the topology. + * + * @return the place holder + */ + public TNodeTemplate getPlaceHolder() { + return placeHolder; + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/package-info.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/package-info.java new file mode 100644 index 0000000..8a097d5 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/completer/package-info.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +/** + * This package contains classes and methods to actually execute the completion for several use cases. + */ +package org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.completer; + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/package-info.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/package-info.java new file mode 100644 index 0000000..bdaf170 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/addons/topologycompleter/topologycompletion/package-info.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +/** + * This package contains classes and methods to execute and manage the completion. + */ +package org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion; + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/resources/TopologyCompletionResource.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/resources/TopologyCompletionResource.java new file mode 100644 index 0000000..efc36e3 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/resources/TopologyCompletionResource.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +package org.eclipse.winery.topologymodeler.resources; + +import java.io.StringReader; + +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; + +import org.eclipse.winery.model.tosca.Definitions; +import org.eclipse.winery.model.tosca.TNodeTemplate; +import org.eclipse.winery.model.tosca.TRelationshipTemplate; +import org.eclipse.winery.model.tosca.TServiceTemplate; +import org.eclipse.winery.model.tosca.TTopologyTemplate; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.JAXBHelper; +import org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.RESTHelper; + +/** + * This class contains resources used for the topology completion. + * + */ +@Path("/") +public class TopologyCompletionResource { + + /** + * Adds selected {@link TNodeTemplate}s and {@link TRelationshipTemplate}s + * to a topology. + * + * @param topology + * the {@link TTopologyTemplate} as XML string + * @param allChoices + * all possible choices as XML + * @param selectedNodeTemplates + * the selected {@link TNodeTemplate}s as JSON array + * @param selectedRelationshipTemplates + * the selected {@link TRelationshipTemplate}s as JSON array + * @return the enhanced {@link TTopologyTemplate} + */ + @Path("selectionhandler/") + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response handleSelection( + @QueryParam(value = "topology") String topology, + @QueryParam(value = "allChoices") String allChoices, + @QueryParam(value = "selectedNodeTemplates") String selectedNodeTemplates, + @QueryParam(value = "selectedRelationshipTemplates") String selectedRelationshipTemplates) { + return Response + .ok() + .entity(JAXBHelper.addTemplatesToTopology(topology, allChoices, + selectedNodeTemplates, selectedRelationshipTemplates)) + .build(); + } + + /** + * This resource is used to save a {@link TTopologyTemplate} to the repository. + * + * @param topology + * the topology to be saved + * @param templateURL + * the URL the {@link TTopologyTemplate} of the topology template + * @param repositoryURL + * the URL of the repository + * @param topologyName + * the name of the saved {@link TTopologyTemplate} + * @param topologyNamespace + * the namespace of the saved {@link TTopologyTemplate} + * @param overwriteTopology + * whether the {@link TTopologyTemplate} should be overwritten or not + * + * @return whether the save operation has been successful or not + */ + @Path("topologysaver/") + @POST + public Response saveTopology(@FormParam("topology") String topology, + @FormParam(value = "templateURL") String templateURL, + @FormParam(value = "repositoryURL") String repositoryURL, + @FormParam(value = "topologyName") String topologyName, + @FormParam(value = "topologyNamespace") String topologyNamespace, + @FormParam(value = "overwriteTopology") String overwriteTopology) { + try { + + boolean overwrite = Boolean.parseBoolean(overwriteTopology); + + // initiate JaxB context + JAXBContext context; + context = JAXBContext.newInstance(Definitions.class); + StringReader reader = new StringReader(topology); + + // unmarshall the topology XML string + Unmarshaller um; + + um = context.createUnmarshaller(); + + Definitions jaxBDefinitions = (Definitions) um.unmarshal(reader); + TServiceTemplate st = (TServiceTemplate) jaxBDefinitions + .getServiceTemplateOrNodeTypeOrNodeTypeImplementation() + .get(0); + TTopologyTemplate toBeSaved = st.getTopologyTemplate(); + + // depending on the selected save method (overwrite or create new) + // the save method is called + if (overwrite) { + RESTHelper.saveCompleteTopology(toBeSaved, templateURL, true, + "", "", repositoryURL); + } else { + RESTHelper.saveCompleteTopology(toBeSaved, templateURL, false, + topologyName, topologyNamespace, repositoryURL); + } + + return Response.ok().build(); + + } catch (JAXBException e) { + e.printStackTrace(); + return Response.serverError().build(); + } + } +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/resources/package-info.java b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/resources/package-info.java new file mode 100644 index 0000000..a06da46 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/java/org/eclipse/winery/topologymodeler/resources/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains the resources of the topology modeler + */ +package org.eclipse.winery.topologymodeler.resources;
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/resources/.gitignore b/winery/org.eclipse.winery.topologymodeler/src/main/resources/.gitignore new file mode 100644 index 0000000..17ae84b --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/resources/.gitignore @@ -0,0 +1 @@ +rebel.xml diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/3rdparty/jquery-ui/js/jquery-ui.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/3rdparty/jquery-ui/js/jquery-ui.js new file mode 100644 index 0000000..6eccbfe --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/3rdparty/jquery-ui/js/jquery-ui.js @@ -0,0 +1,14987 @@ +/*! jQuery UI - v1.10.2 - 2013-03-14 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.effect.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.position.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js +* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ +(function( $, undefined ) { + +var uuid = 0, + runiqueId = /^ui-id-\d+$/; + +// $.ui might exist from components with no dependencies, e.g., $.ui.position +$.ui = $.ui || {}; + +$.extend( $.ui, { + version: "1.10.2", + + keyCode: { + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + NUMPAD_ADD: 107, + NUMPAD_DECIMAL: 110, + NUMPAD_DIVIDE: 111, + NUMPAD_ENTER: 108, + NUMPAD_MULTIPLY: 106, + NUMPAD_SUBTRACT: 109, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 + } +}); + +// plugins +$.fn.extend({ + focus: (function( orig ) { + return function( delay, fn ) { + return typeof delay === "number" ? + this.each(function() { + var elem = this; + setTimeout(function() { + $( elem ).focus(); + if ( fn ) { + fn.call( elem ); + } + }, delay ); + }) : + orig.apply( this, arguments ); + }; + })( $.fn.focus ), + + scrollParent: function() { + var scrollParent; + if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { + scrollParent = this.parents().filter(function() { + return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); + }).eq(0); + } else { + scrollParent = this.parents().filter(function() { + return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); + }).eq(0); + } + + return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent; + }, + + zIndex: function( zIndex ) { + if ( zIndex !== undefined ) { + return this.css( "zIndex", zIndex ); + } + + if ( this.length ) { + var elem = $( this[ 0 ] ), position, value; + while ( elem.length && elem[ 0 ] !== document ) { + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> + value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } + } + + return 0; + }, + + uniqueId: function() { + return this.each(function() { + if ( !this.id ) { + this.id = "ui-id-" + (++uuid); + } + }); + }, + + removeUniqueId: function() { + return this.each(function() { + if ( runiqueId.test( this.id ) ) { + $( this ).removeAttr( "id" ); + } + }); + } +}); + +// selectors +function focusable( element, isTabIndexNotNaN ) { + var map, mapName, img, + nodeName = element.nodeName.toLowerCase(); + if ( "area" === nodeName ) { + map = element.parentNode; + mapName = map.name; + if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { + return false; + } + img = $( "img[usemap=#" + mapName + "]" )[0]; + return !!img && visible( img ); + } + return ( /input|select|textarea|button|object/.test( nodeName ) ? + !element.disabled : + "a" === nodeName ? + element.href || isTabIndexNotNaN : + isTabIndexNotNaN) && + // the element and all of its ancestors must be visible + visible( element ); +} + +function visible( element ) { + return $.expr.filters.visible( element ) && + !$( element ).parents().addBack().filter(function() { + return $.css( this, "visibility" ) === "hidden"; + }).length; +} + +$.extend( $.expr[ ":" ], { + data: $.expr.createPseudo ? + $.expr.createPseudo(function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + }) : + // support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + }, + + focusable: function( element ) { + return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); + }, + + tabbable: function( element ) { + var tabIndex = $.attr( element, "tabindex" ), + isTabIndexNaN = isNaN( tabIndex ); + return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); + } +}); + +// support: jQuery <1.8 +if ( !$( "<a>" ).outerWidth( 1 ).jquery ) { + $.each( [ "Width", "Height" ], function( i, name ) { + var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], + type = name.toLowerCase(), + orig = { + innerWidth: $.fn.innerWidth, + innerHeight: $.fn.innerHeight, + outerWidth: $.fn.outerWidth, + outerHeight: $.fn.outerHeight + }; + + function reduce( elem, size, border, margin ) { + $.each( side, function() { + size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; + if ( border ) { + size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; + } + if ( margin ) { + size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; + } + }); + return size; + } + + $.fn[ "inner" + name ] = function( size ) { + if ( size === undefined ) { + return orig[ "inner" + name ].call( this ); + } + + return this.each(function() { + $( this ).css( type, reduce( this, size ) + "px" ); + }); + }; + + $.fn[ "outer" + name] = function( size, margin ) { + if ( typeof size !== "number" ) { + return orig[ "outer" + name ].call( this, size ); + } + + return this.each(function() { + $( this).css( type, reduce( this, size, true, margin ) + "px" ); + }); + }; + }); +} + +// support: jQuery <1.8 +if ( !$.fn.addBack ) { + $.fn.addBack = function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + }; +} + +// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) +if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { + $.fn.removeData = (function( removeData ) { + return function( key ) { + if ( arguments.length ) { + return removeData.call( this, $.camelCase( key ) ); + } else { + return removeData.call( this ); + } + }; + })( $.fn.removeData ); +} + + + + + +// deprecated +$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +$.support.selectstart = "onselectstart" in document.createElement( "div" ); +$.fn.extend({ + disableSelection: function() { + return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + + ".ui-disableSelection", function( event ) { + event.preventDefault(); + }); + }, + + enableSelection: function() { + return this.unbind( ".ui-disableSelection" ); + } +}); + +$.extend( $.ui, { + // $.ui.plugin is deprecated. Use the proxy pattern instead. + plugin: { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args ) { + var i, + set = instance.plugins[ name ]; + if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } + }, + + // only used by resizable + hasScroll: function( el, a ) { + + //If overflow is hidden, the element might have extra content, but the user wants to hide it + if ( $( el ).css( "overflow" ) === "hidden") { + return false; + } + + var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", + has = false; + + if ( el[ scroll ] > 0 ) { + return true; + } + + // TODO: determine which cases actually cause this to happen + // if the element doesn't have the scroll set, see if it's possible to + // set the scroll + el[ scroll ] = 1; + has = ( el[ scroll ] > 0 ); + el[ scroll ] = 0; + return has; + } +}); + +})( jQuery ); + +(function( $, undefined ) { + +var uuid = 0, + slice = Array.prototype.slice, + _cleanData = $.cleanData; +$.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); +}; + +$.widget = function( name, base, prototype ) { + var fullName, existingConstructor, constructor, basePrototype, + // proxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + proxiedPrototype = {}, + namespace = name.split( "." )[ 0 ]; + + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); +}; + +$.widget.extend = function( target ) { + var input = slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.widget.extend.apply( null, [ options ].concat(args) ) : + options; + + if ( isMethodCall ) { + this.each(function() { + var methodValue, + instance = $.data( this, fullName ); + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, fullName, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "<div>", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + // 1.9 BC for #7810 + // TODO remove dual storage + .removeData( this.widgetName ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( value === undefined ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( value === undefined ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + // accept selectors, DOM elements + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^(\w+)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +})( jQuery ); + +(function( $, undefined ) { + +var mouseHandled = false; +$( document ).mouseup( function() { + mouseHandled = false; +}); + +$.widget("ui.mouse", { + version: "1.10.2", + options: { + cancel: "input,textarea,button,select,option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .bind("mousedown."+this.widgetName, function(event) { + return that._mouseDown(event); + }) + .bind("click."+this.widgetName, function(event) { + if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { + $.removeData(event.target, that.widgetName + ".preventClickEvent"); + event.stopImmediatePropagation(); + return false; + } + }); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.unbind("."+this.widgetName); + if ( this._mouseMoveDelegate ) { + $(document) + .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); + } + }, + + _mouseDown: function(event) { + // don't let more than one widget handle mouseStart + if( mouseHandled ) { return; } + + // we may have missed mouseup (out of window) + (this._mouseStarted && this._mouseUp(event)); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = (event.which === 1), + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); + if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if (!this.mouseDelayMet) { + this._mouseDelayTimer = setTimeout(function() { + that.mouseDelayMet = true; + }, this.options.delay); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = (this._mouseStart(event) !== false); + if (!this._mouseStarted) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { + $.removeData(event.target, this.widgetName + ".preventClickEvent"); + } + + // these delegates are required to keep context + this._mouseMoveDelegate = function(event) { + return that._mouseMove(event); + }; + this._mouseUpDelegate = function(event) { + return that._mouseUp(event); + }; + $(document) + .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .bind("mouseup."+this.widgetName, this._mouseUpDelegate); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function(event) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { + return this._mouseUp(event); + } + + if (this._mouseStarted) { + this._mouseDrag(event); + return event.preventDefault(); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = + (this._mouseStart(this._mouseDownEvent, event) !== false); + (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); + } + + return !this._mouseStarted; + }, + + _mouseUp: function(event) { + $(document) + .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) + .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); + + if (this._mouseStarted) { + this._mouseStarted = false; + + if (event.target === this._mouseDownEvent.target) { + $.data(event.target, this.widgetName + ".preventClickEvent", true); + } + + this._mouseStop(event); + } + + return false; + }, + + _mouseDistanceMet: function(event) { + return (Math.max( + Math.abs(this._mouseDownEvent.pageX - event.pageX), + Math.abs(this._mouseDownEvent.pageY - event.pageY) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function(/* event */) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function(/* event */) {}, + _mouseDrag: function(/* event */) {}, + _mouseStop: function(/* event */) {}, + _mouseCapture: function(/* event */) { return true; } +}); + +})(jQuery); + +(function( $, undefined ) { + +$.widget("ui.draggable", $.ui.mouse, { + version: "1.10.2", + widgetEventPrefix: "drag", + options: { + addClasses: true, + appendTo: "parent", + axis: false, + connectToSortable: false, + containment: false, + cursor: "auto", + cursorAt: false, + grid: false, + handle: false, + helper: "original", + iframeFix: false, + opacity: false, + refreshPositions: false, + revert: false, + revertDuration: 500, + scope: "default", + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + snap: false, + snapMode: "both", + snapTolerance: 20, + stack: false, + zIndex: false, + + // callbacks + drag: null, + start: null, + stop: null + }, + _create: function() { + + if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) { + this.element[0].style.position = "relative"; + } + if (this.options.addClasses){ + this.element.addClass("ui-draggable"); + } + if (this.options.disabled){ + this.element.addClass("ui-draggable-disabled"); + } + + this._mouseInit(); + + }, + + _destroy: function() { + this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); + this._mouseDestroy(); + }, + + _mouseCapture: function(event) { + + var o = this.options; + + // among others, prevent a drag on a resizable-handle + if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) { + return false; + } + + //Quit if we're not on a valid handle + this.handle = this._getHandle(event); + if (!this.handle) { + return false; + } + + $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { + $("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>") + .css({ + width: this.offsetWidth+"px", height: this.offsetHeight+"px", + position: "absolute", opacity: "0.001", zIndex: 1000 + }) + .css($(this).offset()) + .appendTo("body"); + }); + + return true; + + }, + + _mouseStart: function(event) { + + var o = this.options; + + //Create and append the visible helper + this.helper = this._createHelper(event); + + this.helper.addClass("ui-draggable-dragging"); + + //Cache the helper size + this._cacheHelperProportions(); + + //If ddmanager is used for droppables, set the global draggable + if($.ui.ddmanager) { + $.ui.ddmanager.current = this; + } + + /* + * - Position generation - + * This block generates everything position related - it's the core of draggables. + */ + + //Cache the margins of the original element + this._cacheMargins(); + + //Store the helper's css position + this.cssPosition = this.helper.css("position"); + this.scrollParent = this.helper.scrollParent(); + + //The element's absolute position on the page minus margins + this.offset = this.positionAbs = this.element.offset(); + this.offset = { + top: this.offset.top - this.margins.top, + left: this.offset.left - this.margins.left + }; + + $.extend(this.offset, { + click: { //Where the click happened, relative to the element + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }, + parent: this._getParentOffset(), + relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper + }); + + //Generate the original position + this.originalPosition = this.position = this._generatePosition(event); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + + //Adjust the mouse offset relative to the helper if "cursorAt" is supplied + (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); + + //Set a containment if given in the options + if(o.containment) { + this._setContainment(); + } + + //Trigger event + callbacks + if(this._trigger("start", event) === false) { + this._clear(); + return false; + } + + //Recache the helper size + this._cacheHelperProportions(); + + //Prepare the droppable offsets + if ($.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + + + this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position + + //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003) + if ( $.ui.ddmanager ) { + $.ui.ddmanager.dragStart(this, event); + } + + return true; + }, + + _mouseDrag: function(event, noPropagation) { + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + //Call plugins and callbacks and use the resulting position if something is returned + if (!noPropagation) { + var ui = this._uiHash(); + if(this._trigger("drag", event, ui) === false) { + this._mouseUp({}); + return false; + } + this.position = ui.position; + } + + if(!this.options.axis || this.options.axis !== "y") { + this.helper[0].style.left = this.position.left+"px"; + } + if(!this.options.axis || this.options.axis !== "x") { + this.helper[0].style.top = this.position.top+"px"; + } + if($.ui.ddmanager) { + $.ui.ddmanager.drag(this, event); + } + + return false; + }, + + _mouseStop: function(event) { + + //If we are using droppables, inform the manager about the drop + var element, + that = this, + elementInDom = false, + dropped = false; + if ($.ui.ddmanager && !this.options.dropBehaviour) { + dropped = $.ui.ddmanager.drop(this, event); + } + + //if a drop comes from outside (a sortable) + if(this.dropped) { + dropped = this.dropped; + this.dropped = false; + } + + //if the original element is no longer in the DOM don't bother to continue (see #8269) + element = this.element[0]; + while ( element && (element = element.parentNode) ) { + if (element === document ) { + elementInDom = true; + } + } + if ( !elementInDom && this.options.helper === "original" ) { + return false; + } + + if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) { + $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() { + if(that._trigger("stop", event) !== false) { + that._clear(); + } + }); + } else { + if(this._trigger("stop", event) !== false) { + this._clear(); + } + } + + return false; + }, + + _mouseUp: function(event) { + //Remove frame helpers + $("div.ui-draggable-iframeFix").each(function() { + this.parentNode.removeChild(this); + }); + + //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003) + if( $.ui.ddmanager ) { + $.ui.ddmanager.dragStop(this, event); + } + + return $.ui.mouse.prototype._mouseUp.call(this, event); + }, + + cancel: function() { + + if(this.helper.is(".ui-draggable-dragging")) { + this._mouseUp({}); + } else { + this._clear(); + } + + return this; + + }, + + _getHandle: function(event) { + return this.options.handle ? + !!$( event.target ).closest( this.element.find( this.options.handle ) ).length : + true; + }, + + _createHelper: function(event) { + + var o = this.options, + helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element); + + if(!helper.parents("body").length) { + helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo)); + } + + if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) { + helper.css("position", "absolute"); + } + + return helper; + + }, + + _adjustOffsetFromHelper: function(obj) { + if (typeof obj === "string") { + obj = obj.split(" "); + } + if ($.isArray(obj)) { + obj = {left: +obj[0], top: +obj[1] || 0}; + } + if ("left" in obj) { + this.offset.click.left = obj.left + this.margins.left; + } + if ("right" in obj) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ("top" in obj) { + this.offset.click.top = obj.top + this.margins.top; + } + if ("bottom" in obj) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _getParentOffset: function() { + + //Get the offsetParent and cache its position + this.offsetParent = this.helper.offsetParent(); + var po = this.offsetParent.offset(); + + // This is a special case where we need to modify a offset calculated on start, since the following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that + // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag + if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + //This needs to be actually done for all browsers, since pageX/pageY includes this information + //Ugly IE fix + if((this.offsetParent[0] === document.body) || + (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { + po = { top: 0, left: 0 }; + } + + return { + top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), + left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) + }; + + }, + + _getRelativeOffset: function() { + + if(this.cssPosition === "relative") { + var p = this.element.position(); + return { + top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), + left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() + }; + } else { + return { top: 0, left: 0 }; + } + + }, + + _cacheMargins: function() { + this.margins = { + left: (parseInt(this.element.css("marginLeft"),10) || 0), + top: (parseInt(this.element.css("marginTop"),10) || 0), + right: (parseInt(this.element.css("marginRight"),10) || 0), + bottom: (parseInt(this.element.css("marginBottom"),10) || 0) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var over, c, ce, + o = this.options; + + if(o.containment === "parent") { + o.containment = this.helper[0].parentNode; + } + if(o.containment === "document" || o.containment === "window") { + this.containment = [ + o.containment === "document" ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left, + o.containment === "document" ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top, + (o.containment === "document" ? 0 : $(window).scrollLeft()) + $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, + (o.containment === "document" ? 0 : $(window).scrollTop()) + ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top + ]; + } + + if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor !== Array) { + c = $(o.containment); + ce = c[0]; + + if(!ce) { + return; + } + + over = ($(ce).css("overflow") !== "hidden"); + + this.containment = [ + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0), + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0), + (over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderRightWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right, + (over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderBottomWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom + ]; + this.relative_container = c; + + } else if(o.containment.constructor === Array) { + this.containment = o.containment; + } + + }, + + _convertPositionTo: function(d, pos) { + + if(!pos) { + pos = this.position; + } + + var mod = d === "absolute" ? 1 : -1, + scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + return { + top: ( + pos.top + // The absolute mouse position + this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) + ), + left: ( + pos.left + // The absolute mouse position + this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) + ) + }; + + }, + + _generatePosition: function(event) { + + var containment, co, top, left, + o = this.options, + scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, + scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName), + pageX = event.pageX, + pageY = event.pageY; + + /* + * - Position constraining - + * Constrain the position to a mix of grid, containment. + */ + + if(this.originalPosition) { //If we are not dragging yet, we won't check for options + if(this.containment) { + if (this.relative_container){ + co = this.relative_container.offset(); + containment = [ this.containment[0] + co.left, + this.containment[1] + co.top, + this.containment[2] + co.left, + this.containment[3] + co.top ]; + } + else { + containment = this.containment; + } + + if(event.pageX - this.offset.click.left < containment[0]) { + pageX = containment[0] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top < containment[1]) { + pageY = containment[1] + this.offset.click.top; + } + if(event.pageX - this.offset.click.left > containment[2]) { + pageX = containment[2] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top > containment[3]) { + pageY = containment[3] + this.offset.click.top; + } + } + + if(o.grid) { + //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950) + top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY; + pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; + + left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX; + pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; + } + + } + + return { + top: ( + pageY - // The absolute mouse position + this.offset.click.top - // Click offset (relative to the element) + this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top + // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) + ), + left: ( + pageX - // The absolute mouse position + this.offset.click.left - // Click offset (relative to the element) + this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left + // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) + ) + }; + + }, + + _clear: function() { + this.helper.removeClass("ui-draggable-dragging"); + if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) { + this.helper.remove(); + } + this.helper = null; + this.cancelHelperRemoval = false; + }, + + // From now on bulk stuff - mainly helpers + + _trigger: function(type, event, ui) { + ui = ui || this._uiHash(); + $.ui.plugin.call(this, type, [event, ui]); + //The absolute position has to be recalculated after plugins + if(type === "drag") { + this.positionAbs = this._convertPositionTo("absolute"); + } + return $.Widget.prototype._trigger.call(this, type, event, ui); + }, + + plugins: {}, + + _uiHash: function() { + return { + helper: this.helper, + position: this.position, + originalPosition: this.originalPosition, + offset: this.positionAbs + }; + } + +}); + +$.ui.plugin.add("draggable", "connectToSortable", { + start: function(event, ui) { + + var inst = $(this).data("ui-draggable"), o = inst.options, + uiSortable = $.extend({}, ui, { item: inst.element }); + inst.sortables = []; + $(o.connectToSortable).each(function() { + var sortable = $.data(this, "ui-sortable"); + if (sortable && !sortable.options.disabled) { + inst.sortables.push({ + instance: sortable, + shouldRevert: sortable.options.revert + }); + sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page). + sortable._trigger("activate", event, uiSortable); + } + }); + + }, + stop: function(event, ui) { + + //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper + var inst = $(this).data("ui-draggable"), + uiSortable = $.extend({}, ui, { item: inst.element }); + + $.each(inst.sortables, function() { + if(this.instance.isOver) { + + this.instance.isOver = 0; + + inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance + this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work) + + //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid" + if(this.shouldRevert) { + this.instance.options.revert = this.shouldRevert; + } + + //Trigger the stop of the sortable + this.instance._mouseStop(event); + + this.instance.options.helper = this.instance.options._helper; + + //If the helper has been the original item, restore properties in the sortable + if(inst.options.helper === "original") { + this.instance.currentItem.css({ top: "auto", left: "auto" }); + } + + } else { + this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance + this.instance._trigger("deactivate", event, uiSortable); + } + + }); + + }, + drag: function(event, ui) { + + var inst = $(this).data("ui-draggable"), that = this; + + $.each(inst.sortables, function() { + + var innermostIntersecting = false, + thisSortable = this; + + //Copy over some variables to allow calling the sortable's native _intersectsWith + this.instance.positionAbs = inst.positionAbs; + this.instance.helperProportions = inst.helperProportions; + this.instance.offset.click = inst.offset.click; + + if(this.instance._intersectsWith(this.instance.containerCache)) { + innermostIntersecting = true; + $.each(inst.sortables, function () { + this.instance.positionAbs = inst.positionAbs; + this.instance.helperProportions = inst.helperProportions; + this.instance.offset.click = inst.offset.click; + if (this !== thisSortable && + this.instance._intersectsWith(this.instance.containerCache) && + $.contains(thisSortable.instance.element[0], this.instance.element[0]) + ) { + innermostIntersecting = false; + } + return innermostIntersecting; + }); + } + + + if(innermostIntersecting) { + //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once + if(!this.instance.isOver) { + + this.instance.isOver = 1; + //Now we fake the start of dragging for the sortable instance, + //by cloning the list group item, appending it to the sortable and using it as inst.currentItem + //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one) + this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true); + this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it + this.instance.options.helper = function() { return ui.helper[0]; }; + + event.target = this.instance.currentItem[0]; + this.instance._mouseCapture(event, true); + this.instance._mouseStart(event, true, true); + + //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes + this.instance.offset.click.top = inst.offset.click.top; + this.instance.offset.click.left = inst.offset.click.left; + this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left; + this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top; + + inst._trigger("toSortable", event); + inst.dropped = this.instance.element; //draggable revert needs that + //hack so receive/update callbacks work (mostly) + inst.currentItem = inst.element; + this.instance.fromOutside = inst; + + } + + //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable + if(this.instance.currentItem) { + this.instance._mouseDrag(event); + } + + } else { + + //If it doesn't intersect with the sortable, and it intersected before, + //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval + if(this.instance.isOver) { + + this.instance.isOver = 0; + this.instance.cancelHelperRemoval = true; + + //Prevent reverting on this forced stop + this.instance.options.revert = false; + + // The out event needs to be triggered independently + this.instance._trigger("out", event, this.instance._uiHash(this.instance)); + + this.instance._mouseStop(event, true); + this.instance.options.helper = this.instance.options._helper; + + //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size + this.instance.currentItem.remove(); + if(this.instance.placeholder) { + this.instance.placeholder.remove(); + } + + inst._trigger("fromSortable", event); + inst.dropped = false; //draggable revert needs that + } + + } + + }); + + } +}); + +$.ui.plugin.add("draggable", "cursor", { + start: function() { + var t = $("body"), o = $(this).data("ui-draggable").options; + if (t.css("cursor")) { + o._cursor = t.css("cursor"); + } + t.css("cursor", o.cursor); + }, + stop: function() { + var o = $(this).data("ui-draggable").options; + if (o._cursor) { + $("body").css("cursor", o._cursor); + } + } +}); + +$.ui.plugin.add("draggable", "opacity", { + start: function(event, ui) { + var t = $(ui.helper), o = $(this).data("ui-draggable").options; + if(t.css("opacity")) { + o._opacity = t.css("opacity"); + } + t.css("opacity", o.opacity); + }, + stop: function(event, ui) { + var o = $(this).data("ui-draggable").options; + if(o._opacity) { + $(ui.helper).css("opacity", o._opacity); + } + } +}); + +$.ui.plugin.add("draggable", "scroll", { + start: function() { + var i = $(this).data("ui-draggable"); + if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { + i.overflowOffset = i.scrollParent.offset(); + } + }, + drag: function( event ) { + + var i = $(this).data("ui-draggable"), o = i.options, scrolled = false; + + if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { + + if(!o.axis || o.axis !== "x") { + if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { + i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed; + } else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) { + i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed; + } + } + + if(!o.axis || o.axis !== "y") { + if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { + i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed; + } else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) { + i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed; + } + } + + } else { + + if(!o.axis || o.axis !== "x") { + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + } + } + + if(!o.axis || o.axis !== "y") { + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + } + } + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(i, event); + } + + } +}); + +$.ui.plugin.add("draggable", "snap", { + start: function() { + + var i = $(this).data("ui-draggable"), + o = i.options; + + i.snapElements = []; + + $(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() { + var $t = $(this), + $o = $t.offset(); + if(this !== i.element[0]) { + i.snapElements.push({ + item: this, + width: $t.outerWidth(), height: $t.outerHeight(), + top: $o.top, left: $o.left + }); + } + }); + + }, + drag: function(event, ui) { + + var ts, bs, ls, rs, l, r, t, b, i, first, + inst = $(this).data("ui-draggable"), + o = inst.options, + d = o.snapTolerance, + x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, + y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; + + for (i = inst.snapElements.length - 1; i >= 0; i--){ + + l = inst.snapElements[i].left; + r = l + inst.snapElements[i].width; + t = inst.snapElements[i].top; + b = t + inst.snapElements[i].height; + + //Yes, I know, this is insane ;) + if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) { + if(inst.snapElements[i].snapping) { + (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); + } + inst.snapElements[i].snapping = false; + continue; + } + + if(o.snapMode !== "inner") { + ts = Math.abs(t - y2) <= d; + bs = Math.abs(b - y1) <= d; + ls = Math.abs(l - x2) <= d; + rs = Math.abs(r - x1) <= d; + if(ts) { + ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top; + } + if(bs) { + ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top; + } + if(ls) { + ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left; + } + if(rs) { + ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left; + } + } + + first = (ts || bs || ls || rs); + + if(o.snapMode !== "outer") { + ts = Math.abs(t - y1) <= d; + bs = Math.abs(b - y2) <= d; + ls = Math.abs(l - x1) <= d; + rs = Math.abs(r - x2) <= d; + if(ts) { + ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top; + } + if(bs) { + ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top; + } + if(ls) { + ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left; + } + if(rs) { + ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left; + } + } + + if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) { + (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); + } + inst.snapElements[i].snapping = (ts || bs || ls || rs || first); + + } + + } +}); + +$.ui.plugin.add("draggable", "stack", { + start: function() { + var min, + o = this.data("ui-draggable").options, + group = $.makeArray($(o.stack)).sort(function(a,b) { + return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0); + }); + + if (!group.length) { return; } + + min = parseInt($(group[0]).css("zIndex"), 10) || 0; + $(group).each(function(i) { + $(this).css("zIndex", min + i); + }); + this.css("zIndex", (min + group.length)); + } +}); + +$.ui.plugin.add("draggable", "zIndex", { + start: function(event, ui) { + var t = $(ui.helper), o = $(this).data("ui-draggable").options; + if(t.css("zIndex")) { + o._zIndex = t.css("zIndex"); + } + t.css("zIndex", o.zIndex); + }, + stop: function(event, ui) { + var o = $(this).data("ui-draggable").options; + if(o._zIndex) { + $(ui.helper).css("zIndex", o._zIndex); + } + } +}); + +})(jQuery); + +(function( $, undefined ) { + +function isOverAxis( x, reference, size ) { + return ( x > reference ) && ( x < ( reference + size ) ); +} + +$.widget("ui.droppable", { + version: "1.10.2", + widgetEventPrefix: "drop", + options: { + accept: "*", + activeClass: false, + addClasses: true, + greedy: false, + hoverClass: false, + scope: "default", + tolerance: "intersect", + + // callbacks + activate: null, + deactivate: null, + drop: null, + out: null, + over: null + }, + _create: function() { + + var o = this.options, + accept = o.accept; + + this.isover = false; + this.isout = true; + + this.accept = $.isFunction(accept) ? accept : function(d) { + return d.is(accept); + }; + + //Store the droppable's proportions + this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; + + // Add the reference and positions to the manager + $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; + $.ui.ddmanager.droppables[o.scope].push(this); + + (o.addClasses && this.element.addClass("ui-droppable")); + + }, + + _destroy: function() { + var i = 0, + drop = $.ui.ddmanager.droppables[this.options.scope]; + + for ( ; i < drop.length; i++ ) { + if ( drop[i] === this ) { + drop.splice(i, 1); + } + } + + this.element.removeClass("ui-droppable ui-droppable-disabled"); + }, + + _setOption: function(key, value) { + + if(key === "accept") { + this.accept = $.isFunction(value) ? value : function(d) { + return d.is(value); + }; + } + $.Widget.prototype._setOption.apply(this, arguments); + }, + + _activate: function(event) { + var draggable = $.ui.ddmanager.current; + if(this.options.activeClass) { + this.element.addClass(this.options.activeClass); + } + if(draggable){ + this._trigger("activate", event, this.ui(draggable)); + } + }, + + _deactivate: function(event) { + var draggable = $.ui.ddmanager.current; + if(this.options.activeClass) { + this.element.removeClass(this.options.activeClass); + } + if(draggable){ + this._trigger("deactivate", event, this.ui(draggable)); + } + }, + + _over: function(event) { + + var draggable = $.ui.ddmanager.current; + + // Bail if draggable and droppable are same element + if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { + return; + } + + if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.hoverClass) { + this.element.addClass(this.options.hoverClass); + } + this._trigger("over", event, this.ui(draggable)); + } + + }, + + _out: function(event) { + + var draggable = $.ui.ddmanager.current; + + // Bail if draggable and droppable are same element + if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { + return; + } + + if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.hoverClass) { + this.element.removeClass(this.options.hoverClass); + } + this._trigger("out", event, this.ui(draggable)); + } + + }, + + _drop: function(event,custom) { + + var draggable = custom || $.ui.ddmanager.current, + childrenIntersection = false; + + // Bail if draggable and droppable are same element + if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { + return false; + } + + this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() { + var inst = $.data(this, "ui-droppable"); + if( + inst.options.greedy && + !inst.options.disabled && + inst.options.scope === draggable.options.scope && + inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) && + $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) + ) { childrenIntersection = true; return false; } + }); + if(childrenIntersection) { + return false; + } + + if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + if(this.options.activeClass) { + this.element.removeClass(this.options.activeClass); + } + if(this.options.hoverClass) { + this.element.removeClass(this.options.hoverClass); + } + this._trigger("drop", event, this.ui(draggable)); + return this.element; + } + + return false; + + }, + + ui: function(c) { + return { + draggable: (c.currentItem || c.element), + helper: c.helper, + position: c.position, + offset: c.positionAbs + }; + } + +}); + +$.ui.intersect = function(draggable, droppable, toleranceMode) { + + if (!droppable.offset) { + return false; + } + + var draggableLeft, draggableTop, + x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, + y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height, + l = droppable.offset.left, r = l + droppable.proportions.width, + t = droppable.offset.top, b = t + droppable.proportions.height; + + switch (toleranceMode) { + case "fit": + return (l <= x1 && x2 <= r && t <= y1 && y2 <= b); + case "intersect": + return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half + x2 - (draggable.helperProportions.width / 2) < r && // Left Half + t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half + y2 - (draggable.helperProportions.height / 2) < b ); // Top Half + case "pointer": + draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left); + draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top); + return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width ); + case "touch": + return ( + (y1 >= t && y1 <= b) || // Top edge touching + (y2 >= t && y2 <= b) || // Bottom edge touching + (y1 < t && y2 > b) // Surrounded vertically + ) && ( + (x1 >= l && x1 <= r) || // Left edge touching + (x2 >= l && x2 <= r) || // Right edge touching + (x1 < l && x2 > r) // Surrounded horizontally + ); + default: + return false; + } + +}; + +/* + This manager tracks offsets of draggables and droppables +*/ +$.ui.ddmanager = { + current: null, + droppables: { "default": [] }, + prepareOffsets: function(t, event) { + + var i, j, + m = $.ui.ddmanager.droppables[t.options.scope] || [], + type = event ? event.type : null, // workaround for #2317 + list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack(); + + droppablesLoop: for (i = 0; i < m.length; i++) { + + //No disabled and non-accepted + if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) { + continue; + } + + // Filter out elements in the current dragged item + for (j=0; j < list.length; j++) { + if(list[j] === m[i].element[0]) { + m[i].proportions.height = 0; + continue droppablesLoop; + } + } + + m[i].visible = m[i].element.css("display") !== "none"; + if(!m[i].visible) { + continue; + } + + //Activate the droppable if used directly from draggables + if(type === "mousedown") { + m[i]._activate.call(m[i], event); + } + + m[i].offset = m[i].element.offset(); + m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; + + } + + }, + drop: function(draggable, event) { + + var dropped = false; + // Create a copy of the droppables in case the list changes during the drop (#9116) + $.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() { + + if(!this.options) { + return; + } + if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) { + dropped = this._drop.call(this, event) || dropped; + } + + if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { + this.isout = true; + this.isover = false; + this._deactivate.call(this, event); + } + + }); + return dropped; + + }, + dragStart: function( draggable, event ) { + //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003) + draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() { + if( !draggable.options.refreshPositions ) { + $.ui.ddmanager.prepareOffsets( draggable, event ); + } + }); + }, + drag: function(draggable, event) { + + //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. + if(draggable.options.refreshPositions) { + $.ui.ddmanager.prepareOffsets(draggable, event); + } + + //Run through all droppables and check their positions based on specific tolerance options + $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { + + if(this.options.disabled || this.greedyChild || !this.visible) { + return; + } + + var parentInstance, scope, parent, + intersects = $.ui.intersect(draggable, this, this.options.tolerance), + c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null); + if(!c) { + return; + } + + if (this.options.greedy) { + // find droppable parents with same scope + scope = this.options.scope; + parent = this.element.parents(":data(ui-droppable)").filter(function () { + return $.data(this, "ui-droppable").options.scope === scope; + }); + + if (parent.length) { + parentInstance = $.data(parent[0], "ui-droppable"); + parentInstance.greedyChild = (c === "isover"); + } + } + + // we just moved into a greedy child + if (parentInstance && c === "isover") { + parentInstance.isover = false; + parentInstance.isout = true; + parentInstance._out.call(parentInstance, event); + } + + this[c] = true; + this[c === "isout" ? "isover" : "isout"] = false; + this[c === "isover" ? "_over" : "_out"].call(this, event); + + // we just moved out of a greedy child + if (parentInstance && c === "isout") { + parentInstance.isout = false; + parentInstance.isover = true; + parentInstance._over.call(parentInstance, event); + } + }); + + }, + dragStop: function( draggable, event ) { + draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" ); + //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003) + if( !draggable.options.refreshPositions ) { + $.ui.ddmanager.prepareOffsets( draggable, event ); + } + } +}; + +})(jQuery); + +(function( $, undefined ) { + +function num(v) { + return parseInt(v, 10) || 0; +} + +function isNumber(value) { + return !isNaN(parseInt(value, 10)); +} + +$.widget("ui.resizable", $.ui.mouse, { + version: "1.10.2", + widgetEventPrefix: "resize", + options: { + alsoResize: false, + animate: false, + animateDuration: "slow", + animateEasing: "swing", + aspectRatio: false, + autoHide: false, + containment: false, + ghost: false, + grid: false, + handles: "e,s,se", + helper: false, + maxHeight: null, + maxWidth: null, + minHeight: 10, + minWidth: 10, + // See #7960 + zIndex: 90, + + // callbacks + resize: null, + start: null, + stop: null + }, + _create: function() { + + var n, i, handle, axis, hname, + that = this, + o = this.options; + this.element.addClass("ui-resizable"); + + $.extend(this, { + _aspectRatio: !!(o.aspectRatio), + aspectRatio: o.aspectRatio, + originalElement: this.element, + _proportionallyResizeElements: [], + _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null + }); + + //Wrap the element if it cannot hold child nodes + if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) { + + //Create a wrapper element and set the wrapper to the new current internal element + this.element.wrap( + $("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({ + position: this.element.css("position"), + width: this.element.outerWidth(), + height: this.element.outerHeight(), + top: this.element.css("top"), + left: this.element.css("left") + }) + ); + + //Overwrite the original this.element + this.element = this.element.parent().data( + "ui-resizable", this.element.data("ui-resizable") + ); + + this.elementIsWrapper = true; + + //Move margins to the wrapper + this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") }); + this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0}); + + //Prevent Safari textarea resize + this.originalResizeStyle = this.originalElement.css("resize"); + this.originalElement.css("resize", "none"); + + //Push the actual element to our proportionallyResize internal array + this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" })); + + // avoid IE jump (hard set the margin) + this.originalElement.css({ margin: this.originalElement.css("margin") }); + + // fix handlers offset + this._proportionallyResize(); + + } + + this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" }); + if(this.handles.constructor === String) { + + if ( this.handles === "all") { + this.handles = "n,e,s,w,se,sw,ne,nw"; + } + + n = this.handles.split(","); + this.handles = {}; + + for(i = 0; i < n.length; i++) { + + handle = $.trim(n[i]); + hname = "ui-resizable-"+handle; + axis = $("<div class='ui-resizable-handle " + hname + "'></div>"); + + // Apply zIndex to all handles - see #7960 + axis.css({ zIndex: o.zIndex }); + + //TODO : What's going on here? + if ("se" === handle) { + axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se"); + } + + //Insert into internal handles object and append to element + this.handles[handle] = ".ui-resizable-"+handle; + this.element.append(axis); + } + + } + + this._renderAxis = function(target) { + + var i, axis, padPos, padWrapper; + + target = target || this.element; + + for(i in this.handles) { + + if(this.handles[i].constructor === String) { + this.handles[i] = $(this.handles[i], this.element).show(); + } + + //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls) + if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) { + + axis = $(this.handles[i], this.element); + + //Checking the correct pad and border + padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth(); + + //The padding type i have to apply... + padPos = [ "padding", + /ne|nw|n/.test(i) ? "Top" : + /se|sw|s/.test(i) ? "Bottom" : + /^e$/.test(i) ? "Right" : "Left" ].join(""); + + target.css(padPos, padWrapper); + + this._proportionallyResize(); + + } + + //TODO: What's that good for? There's not anything to be executed left + if(!$(this.handles[i]).length) { + continue; + } + } + }; + + //TODO: make renderAxis a prototype function + this._renderAxis(this.element); + + this._handles = $(".ui-resizable-handle", this.element) + .disableSelection(); + + //Matching axis name + this._handles.mouseover(function() { + if (!that.resizing) { + if (this.className) { + axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i); + } + //Axis, default = se + that.axis = axis && axis[1] ? axis[1] : "se"; + } + }); + + //If we want to auto hide the elements + if (o.autoHide) { + this._handles.hide(); + $(this.element) + .addClass("ui-resizable-autohide") + .mouseenter(function() { + if (o.disabled) { + return; + } + $(this).removeClass("ui-resizable-autohide"); + that._handles.show(); + }) + .mouseleave(function(){ + if (o.disabled) { + return; + } + if (!that.resizing) { + $(this).addClass("ui-resizable-autohide"); + that._handles.hide(); + } + }); + } + + //Initialize the mouse interaction + this._mouseInit(); + + }, + + _destroy: function() { + + this._mouseDestroy(); + + var wrapper, + _destroy = function(exp) { + $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing") + .removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove(); + }; + + //TODO: Unwrap at same DOM position + if (this.elementIsWrapper) { + _destroy(this.element); + wrapper = this.element; + this.originalElement.css({ + position: wrapper.css("position"), + width: wrapper.outerWidth(), + height: wrapper.outerHeight(), + top: wrapper.css("top"), + left: wrapper.css("left") + }).insertAfter( wrapper ); + wrapper.remove(); + } + + this.originalElement.css("resize", this.originalResizeStyle); + _destroy(this.originalElement); + + return this; + }, + + _mouseCapture: function(event) { + var i, handle, + capture = false; + + for (i in this.handles) { + handle = $(this.handles[i])[0]; + if (handle === event.target || $.contains(handle, event.target)) { + capture = true; + } + } + + return !this.options.disabled && capture; + }, + + _mouseStart: function(event) { + + var curleft, curtop, cursor, + o = this.options, + iniPos = this.element.position(), + el = this.element; + + this.resizing = true; + + // bugfix for http://dev.jquery.com/ticket/1749 + if ( (/absolute/).test( el.css("position") ) ) { + el.css({ position: "absolute", top: el.css("top"), left: el.css("left") }); + } else if (el.is(".ui-draggable")) { + el.css({ position: "absolute", top: iniPos.top, left: iniPos.left }); + } + + this._renderProxy(); + + curleft = num(this.helper.css("left")); + curtop = num(this.helper.css("top")); + + if (o.containment) { + curleft += $(o.containment).scrollLeft() || 0; + curtop += $(o.containment).scrollTop() || 0; + } + + //Store needed variables + this.offset = this.helper.offset(); + this.position = { left: curleft, top: curtop }; + this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; + this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; + this.originalPosition = { left: curleft, top: curtop }; + this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() }; + this.originalMousePosition = { left: event.pageX, top: event.pageY }; + + //Aspect Ratio + this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1); + + cursor = $(".ui-resizable-" + this.axis).css("cursor"); + $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor); + + el.addClass("ui-resizable-resizing"); + this._propagate("start", event); + return true; + }, + + _mouseDrag: function(event) { + + //Increase performance, avoid regex + var data, + el = this.helper, props = {}, + smp = this.originalMousePosition, + a = this.axis, + prevTop = this.position.top, + prevLeft = this.position.left, + prevWidth = this.size.width, + prevHeight = this.size.height, + dx = (event.pageX-smp.left)||0, + dy = (event.pageY-smp.top)||0, + trigger = this._change[a]; + + if (!trigger) { + return false; + } + + // Calculate the attrs that will be change + data = trigger.apply(this, [event, dx, dy]); + + // Put this in the mouseDrag handler since the user can start pressing shift while resizing + this._updateVirtualBoundaries(event.shiftKey); + if (this._aspectRatio || event.shiftKey) { + data = this._updateRatio(data, event); + } + + data = this._respectSize(data, event); + + this._updateCache(data); + + // plugins callbacks need to be called first + this._propagate("resize", event); + + if (this.position.top !== prevTop) { + props.top = this.position.top + "px"; + } + if (this.position.left !== prevLeft) { + props.left = this.position.left + "px"; + } + if (this.size.width !== prevWidth) { + props.width = this.size.width + "px"; + } + if (this.size.height !== prevHeight) { + props.height = this.size.height + "px"; + } + el.css(props); + + if (!this._helper && this._proportionallyResizeElements.length) { + this._proportionallyResize(); + } + + // Call the user callback if the element was resized + if ( ! $.isEmptyObject(props) ) { + this._trigger("resize", event, this.ui()); + } + + return false; + }, + + _mouseStop: function(event) { + + this.resizing = false; + var pr, ista, soffseth, soffsetw, s, left, top, + o = this.options, that = this; + + if(this._helper) { + + pr = this._proportionallyResizeElements; + ista = pr.length && (/textarea/i).test(pr[0].nodeName); + soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height; + soffsetw = ista ? 0 : that.sizeDiff.width; + + s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) }; + left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null; + top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; + + if (!o.animate) { + this.element.css($.extend(s, { top: top, left: left })); + } + + that.helper.height(that.size.height); + that.helper.width(that.size.width); + + if (this._helper && !o.animate) { + this._proportionallyResize(); + } + } + + $("body").css("cursor", "auto"); + + this.element.removeClass("ui-resizable-resizing"); + + this._propagate("stop", event); + + if (this._helper) { + this.helper.remove(); + } + + return false; + + }, + + _updateVirtualBoundaries: function(forceAspectRatio) { + var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b, + o = this.options; + + b = { + minWidth: isNumber(o.minWidth) ? o.minWidth : 0, + maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity, + minHeight: isNumber(o.minHeight) ? o.minHeight : 0, + maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity + }; + + if(this._aspectRatio || forceAspectRatio) { + // We want to create an enclosing box whose aspect ration is the requested one + // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension + pMinWidth = b.minHeight * this.aspectRatio; + pMinHeight = b.minWidth / this.aspectRatio; + pMaxWidth = b.maxHeight * this.aspectRatio; + pMaxHeight = b.maxWidth / this.aspectRatio; + + if(pMinWidth > b.minWidth) { + b.minWidth = pMinWidth; + } + if(pMinHeight > b.minHeight) { + b.minHeight = pMinHeight; + } + if(pMaxWidth < b.maxWidth) { + b.maxWidth = pMaxWidth; + } + if(pMaxHeight < b.maxHeight) { + b.maxHeight = pMaxHeight; + } + } + this._vBoundaries = b; + }, + + _updateCache: function(data) { + this.offset = this.helper.offset(); + if (isNumber(data.left)) { + this.position.left = data.left; + } + if (isNumber(data.top)) { + this.position.top = data.top; + } + if (isNumber(data.height)) { + this.size.height = data.height; + } + if (isNumber(data.width)) { + this.size.width = data.width; + } + }, + + _updateRatio: function( data ) { + + var cpos = this.position, + csize = this.size, + a = this.axis; + + if (isNumber(data.height)) { + data.width = (data.height * this.aspectRatio); + } else if (isNumber(data.width)) { + data.height = (data.width / this.aspectRatio); + } + + if (a === "sw") { + data.left = cpos.left + (csize.width - data.width); + data.top = null; + } + if (a === "nw") { + data.top = cpos.top + (csize.height - data.height); + data.left = cpos.left + (csize.width - data.width); + } + + return data; + }, + + _respectSize: function( data ) { + + var o = this._vBoundaries, + a = this.axis, + ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height), + isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height), + dw = this.originalPosition.left + this.originalSize.width, + dh = this.position.top + this.size.height, + cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a); + if (isminw) { + data.width = o.minWidth; + } + if (isminh) { + data.height = o.minHeight; + } + if (ismaxw) { + data.width = o.maxWidth; + } + if (ismaxh) { + data.height = o.maxHeight; + } + + if (isminw && cw) { + data.left = dw - o.minWidth; + } + if (ismaxw && cw) { + data.left = dw - o.maxWidth; + } + if (isminh && ch) { + data.top = dh - o.minHeight; + } + if (ismaxh && ch) { + data.top = dh - o.maxHeight; + } + + // fixing jump error on top/left - bug #2330 + if (!data.width && !data.height && !data.left && data.top) { + data.top = null; + } else if (!data.width && !data.height && !data.top && data.left) { + data.left = null; + } + + return data; + }, + + _proportionallyResize: function() { + + if (!this._proportionallyResizeElements.length) { + return; + } + + var i, j, borders, paddings, prel, + element = this.helper || this.element; + + for ( i=0; i < this._proportionallyResizeElements.length; i++) { + + prel = this._proportionallyResizeElements[i]; + + if (!this.borderDif) { + this.borderDif = []; + borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")]; + paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")]; + + for ( j = 0; j < borders.length; j++ ) { + this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 ); + } + } + + prel.css({ + height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0, + width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0 + }); + + } + + }, + + _renderProxy: function() { + + var el = this.element, o = this.options; + this.elementOffset = el.offset(); + + if(this._helper) { + + this.helper = this.helper || $("<div style='overflow:hidden;'></div>"); + + this.helper.addClass(this._helper).css({ + width: this.element.outerWidth() - 1, + height: this.element.outerHeight() - 1, + position: "absolute", + left: this.elementOffset.left +"px", + top: this.elementOffset.top +"px", + zIndex: ++o.zIndex //TODO: Don't modify option + }); + + this.helper + .appendTo("body") + .disableSelection(); + + } else { + this.helper = this.element; + } + + }, + + _change: { + e: function(event, dx) { + return { width: this.originalSize.width + dx }; + }, + w: function(event, dx) { + var cs = this.originalSize, sp = this.originalPosition; + return { left: sp.left + dx, width: cs.width - dx }; + }, + n: function(event, dx, dy) { + var cs = this.originalSize, sp = this.originalPosition; + return { top: sp.top + dy, height: cs.height - dy }; + }, + s: function(event, dx, dy) { + return { height: this.originalSize.height + dy }; + }, + se: function(event, dx, dy) { + return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); + }, + sw: function(event, dx, dy) { + return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); + }, + ne: function(event, dx, dy) { + return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); + }, + nw: function(event, dx, dy) { + return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); + } + }, + + _propagate: function(n, event) { + $.ui.plugin.call(this, n, [event, this.ui()]); + (n !== "resize" && this._trigger(n, event, this.ui())); + }, + + plugins: {}, + + ui: function() { + return { + originalElement: this.originalElement, + element: this.element, + helper: this.helper, + position: this.position, + size: this.size, + originalSize: this.originalSize, + originalPosition: this.originalPosition + }; + } + +}); + +/* + * Resizable Extensions + */ + +$.ui.plugin.add("resizable", "animate", { + + stop: function( event ) { + var that = $(this).data("ui-resizable"), + o = that.options, + pr = that._proportionallyResizeElements, + ista = pr.length && (/textarea/i).test(pr[0].nodeName), + soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height, + soffsetw = ista ? 0 : that.sizeDiff.width, + style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) }, + left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null, + top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; + + that.element.animate( + $.extend(style, top && left ? { top: top, left: left } : {}), { + duration: o.animateDuration, + easing: o.animateEasing, + step: function() { + + var data = { + width: parseInt(that.element.css("width"), 10), + height: parseInt(that.element.css("height"), 10), + top: parseInt(that.element.css("top"), 10), + left: parseInt(that.element.css("left"), 10) + }; + + if (pr && pr.length) { + $(pr[0]).css({ width: data.width, height: data.height }); + } + + // propagating resize, and updating values for each animation step + that._updateCache(data); + that._propagate("resize", event); + + } + } + ); + } + +}); + +$.ui.plugin.add("resizable", "containment", { + + start: function() { + var element, p, co, ch, cw, width, height, + that = $(this).data("ui-resizable"), + o = that.options, + el = that.element, + oc = o.containment, + ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc; + + if (!ce) { + return; + } + + that.containerElement = $(ce); + + if (/document/.test(oc) || oc === document) { + that.containerOffset = { left: 0, top: 0 }; + that.containerPosition = { left: 0, top: 0 }; + + that.parentData = { + element: $(document), left: 0, top: 0, + width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight + }; + } + + // i'm a node, so compute top, left, right, bottom + else { + element = $(ce); + p = []; + $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); }); + + that.containerOffset = element.offset(); + that.containerPosition = element.position(); + that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) }; + + co = that.containerOffset; + ch = that.containerSize.height; + cw = that.containerSize.width; + width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ); + height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch); + + that.parentData = { + element: ce, left: co.left, top: co.top, width: width, height: height + }; + } + }, + + resize: function( event ) { + var woset, hoset, isParent, isOffsetRelative, + that = $(this).data("ui-resizable"), + o = that.options, + co = that.containerOffset, cp = that.position, + pRatio = that._aspectRatio || event.shiftKey, + cop = { top:0, left:0 }, ce = that.containerElement; + + if (ce[0] !== document && (/static/).test(ce.css("position"))) { + cop = co; + } + + if (cp.left < (that._helper ? co.left : 0)) { + that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left)); + if (pRatio) { + that.size.height = that.size.width / that.aspectRatio; + } + that.position.left = o.helper ? co.left : 0; + } + + if (cp.top < (that._helper ? co.top : 0)) { + that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top); + if (pRatio) { + that.size.width = that.size.height * that.aspectRatio; + } + that.position.top = that._helper ? co.top : 0; + } + + that.offset.left = that.parentData.left+that.position.left; + that.offset.top = that.parentData.top+that.position.top; + + woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width ); + hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height ); + + isParent = that.containerElement.get(0) === that.element.parent().get(0); + isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position")); + + if(isParent && isOffsetRelative) { + woset -= that.parentData.left; + } + + if (woset + that.size.width >= that.parentData.width) { + that.size.width = that.parentData.width - woset; + if (pRatio) { + that.size.height = that.size.width / that.aspectRatio; + } + } + + if (hoset + that.size.height >= that.parentData.height) { + that.size.height = that.parentData.height - hoset; + if (pRatio) { + that.size.width = that.size.height * that.aspectRatio; + } + } + }, + + stop: function(){ + var that = $(this).data("ui-resizable"), + o = that.options, + co = that.containerOffset, + cop = that.containerPosition, + ce = that.containerElement, + helper = $(that.helper), + ho = helper.offset(), + w = helper.outerWidth() - that.sizeDiff.width, + h = helper.outerHeight() - that.sizeDiff.height; + + if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) { + $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); + } + + if (that._helper && !o.animate && (/static/).test(ce.css("position"))) { + $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); + } + + } +}); + +$.ui.plugin.add("resizable", "alsoResize", { + + start: function () { + var that = $(this).data("ui-resizable"), + o = that.options, + _store = function (exp) { + $(exp).each(function() { + var el = $(this); + el.data("ui-resizable-alsoresize", { + width: parseInt(el.width(), 10), height: parseInt(el.height(), 10), + left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10) + }); + }); + }; + + if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) { + if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); } + else { $.each(o.alsoResize, function (exp) { _store(exp); }); } + }else{ + _store(o.alsoResize); + } + }, + + resize: function (event, ui) { + var that = $(this).data("ui-resizable"), + o = that.options, + os = that.originalSize, + op = that.originalPosition, + delta = { + height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0, + top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0 + }, + + _alsoResize = function (exp, c) { + $(exp).each(function() { + var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {}, + css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"]; + + $.each(css, function (i, prop) { + var sum = (start[prop]||0) + (delta[prop]||0); + if (sum && sum >= 0) { + style[prop] = sum || null; + } + }); + + el.css(style); + }); + }; + + if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) { + $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); }); + }else{ + _alsoResize(o.alsoResize); + } + }, + + stop: function () { + $(this).removeData("resizable-alsoresize"); + } +}); + +$.ui.plugin.add("resizable", "ghost", { + + start: function() { + + var that = $(this).data("ui-resizable"), o = that.options, cs = that.size; + + that.ghost = that.originalElement.clone(); + that.ghost + .css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 }) + .addClass("ui-resizable-ghost") + .addClass(typeof o.ghost === "string" ? o.ghost : ""); + + that.ghost.appendTo(that.helper); + + }, + + resize: function(){ + var that = $(this).data("ui-resizable"); + if (that.ghost) { + that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width }); + } + }, + + stop: function() { + var that = $(this).data("ui-resizable"); + if (that.ghost && that.helper) { + that.helper.get(0).removeChild(that.ghost.get(0)); + } + } + +}); + +$.ui.plugin.add("resizable", "grid", { + + resize: function() { + var that = $(this).data("ui-resizable"), + o = that.options, + cs = that.size, + os = that.originalSize, + op = that.originalPosition, + a = that.axis, + grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid, + gridX = (grid[0]||1), + gridY = (grid[1]||1), + ox = Math.round((cs.width - os.width) / gridX) * gridX, + oy = Math.round((cs.height - os.height) / gridY) * gridY, + newWidth = os.width + ox, + newHeight = os.height + oy, + isMaxWidth = o.maxWidth && (o.maxWidth < newWidth), + isMaxHeight = o.maxHeight && (o.maxHeight < newHeight), + isMinWidth = o.minWidth && (o.minWidth > newWidth), + isMinHeight = o.minHeight && (o.minHeight > newHeight); + + o.grid = grid; + + if (isMinWidth) { + newWidth = newWidth + gridX; + } + if (isMinHeight) { + newHeight = newHeight + gridY; + } + if (isMaxWidth) { + newWidth = newWidth - gridX; + } + if (isMaxHeight) { + newHeight = newHeight - gridY; + } + + if (/^(se|s|e)$/.test(a)) { + that.size.width = newWidth; + that.size.height = newHeight; + } else if (/^(ne)$/.test(a)) { + that.size.width = newWidth; + that.size.height = newHeight; + that.position.top = op.top - oy; + } else if (/^(sw)$/.test(a)) { + that.size.width = newWidth; + that.size.height = newHeight; + that.position.left = op.left - ox; + } else { + that.size.width = newWidth; + that.size.height = newHeight; + that.position.top = op.top - oy; + that.position.left = op.left - ox; + } + } + +}); + +})(jQuery); + +(function( $, undefined ) { + +$.widget("ui.selectable", $.ui.mouse, { + version: "1.10.2", + options: { + appendTo: "body", + autoRefresh: true, + distance: 0, + filter: "*", + tolerance: "touch", + + // callbacks + selected: null, + selecting: null, + start: null, + stop: null, + unselected: null, + unselecting: null + }, + _create: function() { + var selectees, + that = this; + + this.element.addClass("ui-selectable"); + + this.dragged = false; + + // cache selectee children based on filter + this.refresh = function() { + selectees = $(that.options.filter, that.element[0]); + selectees.addClass("ui-selectee"); + selectees.each(function() { + var $this = $(this), + pos = $this.offset(); + $.data(this, "selectable-item", { + element: this, + $element: $this, + left: pos.left, + top: pos.top, + right: pos.left + $this.outerWidth(), + bottom: pos.top + $this.outerHeight(), + startselected: false, + selected: $this.hasClass("ui-selected"), + selecting: $this.hasClass("ui-selecting"), + unselecting: $this.hasClass("ui-unselecting") + }); + }); + }; + this.refresh(); + + this.selectees = selectees.addClass("ui-selectee"); + + this._mouseInit(); + + this.helper = $("<div class='ui-selectable-helper'></div>"); + }, + + _destroy: function() { + this.selectees + .removeClass("ui-selectee") + .removeData("selectable-item"); + this.element + .removeClass("ui-selectable ui-selectable-disabled"); + this._mouseDestroy(); + }, + + _mouseStart: function(event) { + var that = this, + options = this.options; + + this.opos = [event.pageX, event.pageY]; + + if (this.options.disabled) { + return; + } + + this.selectees = $(options.filter, this.element[0]); + + this._trigger("start", event); + + $(options.appendTo).append(this.helper); + // position helper (lasso) + this.helper.css({ + "left": event.pageX, + "top": event.pageY, + "width": 0, + "height": 0 + }); + + if (options.autoRefresh) { + this.refresh(); + } + + this.selectees.filter(".ui-selected").each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.startselected = true; + if (!event.metaKey && !event.ctrlKey) { + selectee.$element.removeClass("ui-selected"); + selectee.selected = false; + selectee.$element.addClass("ui-unselecting"); + selectee.unselecting = true; + // selectable UNSELECTING callback + that._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + }); + + $(event.target).parents().addBack().each(function() { + var doSelect, + selectee = $.data(this, "selectable-item"); + if (selectee) { + doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected"); + selectee.$element + .removeClass(doSelect ? "ui-unselecting" : "ui-selected") + .addClass(doSelect ? "ui-selecting" : "ui-unselecting"); + selectee.unselecting = !doSelect; + selectee.selecting = doSelect; + selectee.selected = doSelect; + // selectable (UN)SELECTING callback + if (doSelect) { + that._trigger("selecting", event, { + selecting: selectee.element + }); + } else { + that._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + return false; + } + }); + + }, + + _mouseDrag: function(event) { + + this.dragged = true; + + if (this.options.disabled) { + return; + } + + var tmp, + that = this, + options = this.options, + x1 = this.opos[0], + y1 = this.opos[1], + x2 = event.pageX, + y2 = event.pageY; + + if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; } + if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; } + this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1}); + + this.selectees.each(function() { + var selectee = $.data(this, "selectable-item"), + hit = false; + + //prevent helper from being selected if appendTo: selectable + if (!selectee || selectee.element === that.element[0]) { + return; + } + + if (options.tolerance === "touch") { + hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) ); + } else if (options.tolerance === "fit") { + hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2); + } + + if (hit) { + // SELECT + if (selectee.selected) { + selectee.$element.removeClass("ui-selected"); + selectee.selected = false; + } + if (selectee.unselecting) { + selectee.$element.removeClass("ui-unselecting"); + selectee.unselecting = false; + } + if (!selectee.selecting) { + selectee.$element.addClass("ui-selecting"); + selectee.selecting = true; + // selectable SELECTING callback + that._trigger("selecting", event, { + selecting: selectee.element + }); + } + } else { + // UNSELECT + if (selectee.selecting) { + if ((event.metaKey || event.ctrlKey) && selectee.startselected) { + selectee.$element.removeClass("ui-selecting"); + selectee.selecting = false; + selectee.$element.addClass("ui-selected"); + selectee.selected = true; + } else { + selectee.$element.removeClass("ui-selecting"); + selectee.selecting = false; + if (selectee.startselected) { + selectee.$element.addClass("ui-unselecting"); + selectee.unselecting = true; + } + // selectable UNSELECTING callback + that._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + } + if (selectee.selected) { + if (!event.metaKey && !event.ctrlKey && !selectee.startselected) { + selectee.$element.removeClass("ui-selected"); + selectee.selected = false; + + selectee.$element.addClass("ui-unselecting"); + selectee.unselecting = true; + // selectable UNSELECTING callback + that._trigger("unselecting", event, { + unselecting: selectee.element + }); + } + } + } + }); + + return false; + }, + + _mouseStop: function(event) { + var that = this; + + this.dragged = false; + + $(".ui-unselecting", this.element[0]).each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.$element.removeClass("ui-unselecting"); + selectee.unselecting = false; + selectee.startselected = false; + that._trigger("unselected", event, { + unselected: selectee.element + }); + }); + $(".ui-selecting", this.element[0]).each(function() { + var selectee = $.data(this, "selectable-item"); + selectee.$element.removeClass("ui-selecting").addClass("ui-selected"); + selectee.selecting = false; + selectee.selected = true; + selectee.startselected = true; + that._trigger("selected", event, { + selected: selectee.element + }); + }); + this._trigger("stop", event); + + this.helper.remove(); + + return false; + } + +}); + +})(jQuery); + +(function( $, undefined ) { + +/*jshint loopfunc: true */ + +function isOverAxis( x, reference, size ) { + return ( x > reference ) && ( x < ( reference + size ) ); +} + +function isFloating(item) { + return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display")); +} + +$.widget("ui.sortable", $.ui.mouse, { + version: "1.10.2", + widgetEventPrefix: "sort", + ready: false, + options: { + appendTo: "parent", + axis: false, + connectWith: false, + containment: false, + cursor: "auto", + cursorAt: false, + dropOnEmpty: true, + forcePlaceholderSize: false, + forceHelperSize: false, + grid: false, + handle: false, + helper: "original", + items: "> *", + opacity: false, + placeholder: false, + revert: false, + scroll: true, + scrollSensitivity: 20, + scrollSpeed: 20, + scope: "default", + tolerance: "intersect", + zIndex: 1000, + + // callbacks + activate: null, + beforeStop: null, + change: null, + deactivate: null, + out: null, + over: null, + receive: null, + remove: null, + sort: null, + start: null, + stop: null, + update: null + }, + _create: function() { + + var o = this.options; + this.containerCache = {}; + this.element.addClass("ui-sortable"); + + //Get the items + this.refresh(); + + //Let's determine if the items are being displayed horizontally + this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false; + + //Let's determine the parent's offset + this.offset = this.element.offset(); + + //Initialize mouse events for interaction + this._mouseInit(); + + //We're ready to go + this.ready = true; + + }, + + _destroy: function() { + this.element + .removeClass("ui-sortable ui-sortable-disabled"); + this._mouseDestroy(); + + for ( var i = this.items.length - 1; i >= 0; i-- ) { + this.items[i].item.removeData(this.widgetName + "-item"); + } + + return this; + }, + + _setOption: function(key, value){ + if ( key === "disabled" ) { + this.options[ key ] = value; + + this.widget().toggleClass( "ui-sortable-disabled", !!value ); + } else { + // Don't call widget base _setOption for disable as it adds ui-state-disabled class + $.Widget.prototype._setOption.apply(this, arguments); + } + }, + + _mouseCapture: function(event, overrideHandle) { + var currentItem = null, + validHandle = false, + that = this; + + if (this.reverting) { + return false; + } + + if(this.options.disabled || this.options.type === "static") { + return false; + } + + //We have to refresh the items data once first + this._refreshItems(event); + + //Find out if the clicked node (or one of its parents) is a actual item in this.items + $(event.target).parents().each(function() { + if($.data(this, that.widgetName + "-item") === that) { + currentItem = $(this); + return false; + } + }); + if($.data(event.target, that.widgetName + "-item") === that) { + currentItem = $(event.target); + } + + if(!currentItem) { + return false; + } + if(this.options.handle && !overrideHandle) { + $(this.options.handle, currentItem).find("*").addBack().each(function() { + if(this === event.target) { + validHandle = true; + } + }); + if(!validHandle) { + return false; + } + } + + this.currentItem = currentItem; + this._removeCurrentsFromItems(); + return true; + + }, + + _mouseStart: function(event, overrideHandle, noActivation) { + + var i, body, + o = this.options; + + this.currentContainer = this; + + //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture + this.refreshPositions(); + + //Create and append the visible helper + this.helper = this._createHelper(event); + + //Cache the helper size + this._cacheHelperProportions(); + + /* + * - Position generation - + * This block generates everything position related - it's the core of draggables. + */ + + //Cache the margins of the original element + this._cacheMargins(); + + //Get the next scrolling parent + this.scrollParent = this.helper.scrollParent(); + + //The element's absolute position on the page minus margins + this.offset = this.currentItem.offset(); + this.offset = { + top: this.offset.top - this.margins.top, + left: this.offset.left - this.margins.left + }; + + $.extend(this.offset, { + click: { //Where the click happened, relative to the element + left: event.pageX - this.offset.left, + top: event.pageY - this.offset.top + }, + parent: this._getParentOffset(), + relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper + }); + + // Only after we got the offset, we can change the helper's position to absolute + // TODO: Still need to figure out a way to make relative sorting possible + this.helper.css("position", "absolute"); + this.cssPosition = this.helper.css("position"); + + //Generate the original position + this.originalPosition = this._generatePosition(event); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + + //Adjust the mouse offset relative to the helper if "cursorAt" is supplied + (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); + + //Cache the former DOM position + this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; + + //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way + if(this.helper[0] !== this.currentItem[0]) { + this.currentItem.hide(); + } + + //Create the placeholder + this._createPlaceholder(); + + //Set a containment if given in the options + if(o.containment) { + this._setContainment(); + } + + if( o.cursor && o.cursor !== "auto" ) { // cursor option + body = this.document.find( "body" ); + + // support: IE + this.storedCursor = body.css( "cursor" ); + body.css( "cursor", o.cursor ); + + this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body ); + } + + if(o.opacity) { // opacity option + if (this.helper.css("opacity")) { + this._storedOpacity = this.helper.css("opacity"); + } + this.helper.css("opacity", o.opacity); + } + + if(o.zIndex) { // zIndex option + if (this.helper.css("zIndex")) { + this._storedZIndex = this.helper.css("zIndex"); + } + this.helper.css("zIndex", o.zIndex); + } + + //Prepare scrolling + if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { + this.overflowOffset = this.scrollParent.offset(); + } + + //Call callbacks + this._trigger("start", event, this._uiHash()); + + //Recache the helper size + if(!this._preserveHelperProportions) { + this._cacheHelperProportions(); + } + + + //Post "activate" events to possible containers + if( !noActivation ) { + for ( i = this.containers.length - 1; i >= 0; i-- ) { + this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); + } + } + + //Prepare possible droppables + if($.ui.ddmanager) { + $.ui.ddmanager.current = this; + } + + if ($.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + + this.dragging = true; + + this.helper.addClass("ui-sortable-helper"); + this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position + return true; + + }, + + _mouseDrag: function(event) { + var i, item, itemElement, intersection, + o = this.options, + scrolled = false; + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + if (!this.lastPositionAbs) { + this.lastPositionAbs = this.positionAbs; + } + + //Do scrolling + if(this.options.scroll) { + if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { + + if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; + } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) { + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; + } + + if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; + } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) { + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; + } + + } else { + + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + } + + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + } + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { + $.ui.ddmanager.prepareOffsets(this, event); + } + } + + //Regenerate the absolute position used for position checks + this.positionAbs = this._convertPositionTo("absolute"); + + //Set the helper position + if(!this.options.axis || this.options.axis !== "y") { + this.helper[0].style.left = this.position.left+"px"; + } + if(!this.options.axis || this.options.axis !== "x") { + this.helper[0].style.top = this.position.top+"px"; + } + + //Rearrange + for (i = this.items.length - 1; i >= 0; i--) { + + //Cache variables and intersection, continue if no intersection + item = this.items[i]; + itemElement = item.item[0]; + intersection = this._intersectsWithPointer(item); + if (!intersection) { + continue; + } + + // Only put the placeholder inside the current Container, skip all + // items form other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this moving items in "sub-sortables" can cause the placeholder to jitter + // beetween the outer and inner container. + if (item.instance !== this.currentContainer) { + continue; + } + + // cannot intersect with itself + // no useless actions that have been done before + // no action if the item moved is the parent of the item checked + if (itemElement !== this.currentItem[0] && + this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement && + !$.contains(this.placeholder[0], itemElement) && + (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true) + ) { + + this.direction = intersection === 1 ? "down" : "up"; + + if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) { + this._rearrange(event, item); + } else { + break; + } + + this._trigger("change", event, this._uiHash()); + break; + } + } + + //Post events to containers + this._contactContainers(event); + + //Interconnect with droppables + if($.ui.ddmanager) { + $.ui.ddmanager.drag(this, event); + } + + //Call callbacks + this._trigger("sort", event, this._uiHash()); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + _mouseStop: function(event, noPropagation) { + + if(!event) { + return; + } + + //If we are using droppables, inform the manager about the drop + if ($.ui.ddmanager && !this.options.dropBehaviour) { + $.ui.ddmanager.drop(this, event); + } + + if(this.options.revert) { + var that = this, + cur = this.placeholder.offset(), + axis = this.options.axis, + animation = {}; + + if ( !axis || axis === "x" ) { + animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft); + } + if ( !axis || axis === "y" ) { + animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop); + } + this.reverting = true; + $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() { + that._clear(event); + }); + } else { + this._clear(event, noPropagation); + } + + return false; + + }, + + cancel: function() { + + if(this.dragging) { + + this._mouseUp({ target: null }); + + if(this.options.helper === "original") { + this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); + } else { + this.currentItem.show(); + } + + //Post deactivating events to containers + for (var i = this.containers.length - 1; i >= 0; i--){ + this.containers[i]._trigger("deactivate", null, this._uiHash(this)); + if(this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", null, this._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + if (this.placeholder) { + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! + if(this.placeholder[0].parentNode) { + this.placeholder[0].parentNode.removeChild(this.placeholder[0]); + } + if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) { + this.helper.remove(); + } + + $.extend(this, { + helper: null, + dragging: false, + reverting: false, + _noFinalSort: null + }); + + if(this.domPosition.prev) { + $(this.domPosition.prev).after(this.currentItem); + } else { + $(this.domPosition.parent).prepend(this.currentItem); + } + } + + return this; + + }, + + serialize: function(o) { + + var items = this._getItemsAsjQuery(o && o.connected), + str = []; + o = o || {}; + + $(items).each(function() { + var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/)); + if (res) { + str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2])); + } + }); + + if(!str.length && o.key) { + str.push(o.key + "="); + } + + return str.join("&"); + + }, + + toArray: function(o) { + + var items = this._getItemsAsjQuery(o && o.connected), + ret = []; + + o = o || {}; + + items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); }); + return ret; + + }, + + /* Be careful with the following core functions */ + _intersectsWith: function(item) { + + var x1 = this.positionAbs.left, + x2 = x1 + this.helperProportions.width, + y1 = this.positionAbs.top, + y2 = y1 + this.helperProportions.height, + l = item.left, + r = l + item.width, + t = item.top, + b = t + item.height, + dyClick = this.offset.click.top, + dxClick = this.offset.click.left, + isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r; + + if ( this.options.tolerance === "pointer" || + this.options.forcePointerForContainers || + (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"]) + ) { + return isOverElement; + } else { + + return (l < x1 + (this.helperProportions.width / 2) && // Right Half + x2 - (this.helperProportions.width / 2) < r && // Left Half + t < y1 + (this.helperProportions.height / 2) && // Bottom Half + y2 - (this.helperProportions.height / 2) < b ); // Top Half + + } + }, + + _intersectsWithPointer: function(item) { + + var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), + isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), + isOverElement = isOverElementHeight && isOverElementWidth, + verticalDirection = this._getDragVerticalDirection(), + horizontalDirection = this._getDragHorizontalDirection(); + + if (!isOverElement) { + return false; + } + + return this.floating ? + ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 ) + : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) ); + + }, + + _intersectsWithSides: function(item) { + + var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), + isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), + verticalDirection = this._getDragVerticalDirection(), + horizontalDirection = this._getDragHorizontalDirection(); + + if (this.floating && horizontalDirection) { + return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf)); + } else { + return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf)); + } + + }, + + _getDragVerticalDirection: function() { + var delta = this.positionAbs.top - this.lastPositionAbs.top; + return delta !== 0 && (delta > 0 ? "down" : "up"); + }, + + _getDragHorizontalDirection: function() { + var delta = this.positionAbs.left - this.lastPositionAbs.left; + return delta !== 0 && (delta > 0 ? "right" : "left"); + }, + + refresh: function(event) { + this._refreshItems(event); + this.refreshPositions(); + return this; + }, + + _connectWith: function() { + var options = this.options; + return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith; + }, + + _getItemsAsjQuery: function(connected) { + + var i, j, cur, inst, + items = [], + queries = [], + connectWith = this._connectWith(); + + if(connectWith && connected) { + for (i = connectWith.length - 1; i >= 0; i--){ + cur = $(connectWith[i]); + for ( j = cur.length - 1; j >= 0; j--){ + inst = $.data(cur[j], this.widgetFullName); + if(inst && inst !== this && !inst.options.disabled) { + queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]); + } + } + } + } + + queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]); + + for (i = queries.length - 1; i >= 0; i--){ + queries[i][0].each(function() { + items.push(this); + }); + } + + return $(items); + + }, + + _removeCurrentsFromItems: function() { + + var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); + + this.items = $.grep(this.items, function (item) { + for (var j=0; j < list.length; j++) { + if(list[j] === item.item[0]) { + return false; + } + } + return true; + }); + + }, + + _refreshItems: function(event) { + + this.items = []; + this.containers = [this]; + + var i, j, cur, inst, targetData, _queries, item, queriesLength, + items = this.items, + queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]], + connectWith = this._connectWith(); + + if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down + for (i = connectWith.length - 1; i >= 0; i--){ + cur = $(connectWith[i]); + for (j = cur.length - 1; j >= 0; j--){ + inst = $.data(cur[j], this.widgetFullName); + if(inst && inst !== this && !inst.options.disabled) { + queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); + this.containers.push(inst); + } + } + } + } + + for (i = queries.length - 1; i >= 0; i--) { + targetData = queries[i][1]; + _queries = queries[i][0]; + + for (j=0, queriesLength = _queries.length; j < queriesLength; j++) { + item = $(_queries[j]); + + item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) + + items.push({ + item: item, + instance: targetData, + width: 0, height: 0, + left: 0, top: 0 + }); + } + } + + }, + + refreshPositions: function(fast) { + + //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change + if(this.offsetParent && this.helper) { + this.offset.parent = this._getParentOffset(); + } + + var i, item, t, p; + + for (i = this.items.length - 1; i >= 0; i--){ + item = this.items[i]; + + //We ignore calculating positions of all connected containers when we're not over them + if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { + continue; + } + + t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; + + if (!fast) { + item.width = t.outerWidth(); + item.height = t.outerHeight(); + } + + p = t.offset(); + item.left = p.left; + item.top = p.top; + } + + if(this.options.custom && this.options.custom.refreshContainers) { + this.options.custom.refreshContainers.call(this); + } else { + for (i = this.containers.length - 1; i >= 0; i--){ + p = this.containers[i].element.offset(); + this.containers[i].containerCache.left = p.left; + this.containers[i].containerCache.top = p.top; + this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); + this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); + } + } + + return this; + }, + + _createPlaceholder: function(that) { + that = that || this; + var className, + o = that.options; + + if(!o.placeholder || o.placeholder.constructor === String) { + className = o.placeholder; + o.placeholder = { + element: function() { + + var nodeName = that.currentItem[0].nodeName.toLowerCase(), + element = $( that.document[0].createElement( nodeName ) ) + .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder") + .removeClass("ui-sortable-helper"); + + if ( nodeName === "tr" ) { + // Use a high colspan to force the td to expand the full + // width of the table (browsers are smart enough to + // handle this properly) + element.append( "<td colspan='99'> </td>" ); + } else if ( nodeName === "img" ) { + element.attr( "src", that.currentItem.attr( "src" ) ); + } + + if ( !className ) { + element.css( "visibility", "hidden" ); + } + + return element; + }, + update: function(container, p) { + + // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that + // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified + if(className && !o.forcePlaceholderSize) { + return; + } + + //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item + if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); } + if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); } + } + }; + } + + //Create the placeholder + that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem)); + + //Append it after the actual current item + that.currentItem.after(that.placeholder); + + //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) + o.placeholder.update(that, that.placeholder); + + }, + + _contactContainers: function(event) { + var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating, + innermostContainer = null, + innermostIndex = null; + + // get innermost container that intersects with item + for (i = this.containers.length - 1; i >= 0; i--) { + + // never consider a container that's located within the item itself + if($.contains(this.currentItem[0], this.containers[i].element[0])) { + continue; + } + + if(this._intersectsWith(this.containers[i].containerCache)) { + + // if we've already found a container and it's more "inner" than this, then continue + if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) { + continue; + } + + innermostContainer = this.containers[i]; + innermostIndex = i; + + } else { + // container doesn't intersect. trigger "out" event if necessary + if(this.containers[i].containerCache.over) { + this.containers[i]._trigger("out", event, this._uiHash(this)); + this.containers[i].containerCache.over = 0; + } + } + + } + + // if no intersecting containers found, return + if(!innermostContainer) { + return; + } + + // move the item into the container if it's not there already + if(this.containers.length === 1) { + if (!this.containers[innermostIndex].containerCache.over) { + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + } else { + + //When entering a new container, we will find the item with the least distance and append our item near it + dist = 10000; + itemWithLeastDistance = null; + floating = innermostContainer.floating || isFloating(this.currentItem); + posProperty = floating ? "left" : "top"; + sizeProperty = floating ? "width" : "height"; + base = this.positionAbs[posProperty] + this.offset.click[posProperty]; + for (j = this.items.length - 1; j >= 0; j--) { + if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) { + continue; + } + if(this.items[j].item[0] === this.currentItem[0]) { + continue; + } + if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) { + continue; + } + cur = this.items[j].item.offset()[posProperty]; + nearBottom = false; + if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){ + nearBottom = true; + cur += this.items[j][sizeProperty]; + } + + if(Math.abs(cur - base) < dist) { + dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; + this.direction = nearBottom ? "up": "down"; + } + } + + //Check if dropOnEmpty is enabled + if(!itemWithLeastDistance && !this.options.dropOnEmpty) { + return; + } + + if(this.currentContainer === this.containers[innermostIndex]) { + return; + } + + itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); + this._trigger("change", event, this._uiHash()); + this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); + this.currentContainer = this.containers[innermostIndex]; + + //Update the placeholder + this.options.placeholder.update(this.currentContainer, this.placeholder); + + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } + + + }, + + _createHelper: function(event) { + + var o = this.options, + helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem); + + //Add the helper to the DOM if that didn't happen already + if(!helper.parents("body").length) { + $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); + } + + if(helper[0] === this.currentItem[0]) { + this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; + } + + if(!helper[0].style.width || o.forceHelperSize) { + helper.width(this.currentItem.width()); + } + if(!helper[0].style.height || o.forceHelperSize) { + helper.height(this.currentItem.height()); + } + + return helper; + + }, + + _adjustOffsetFromHelper: function(obj) { + if (typeof obj === "string") { + obj = obj.split(" "); + } + if ($.isArray(obj)) { + obj = {left: +obj[0], top: +obj[1] || 0}; + } + if ("left" in obj) { + this.offset.click.left = obj.left + this.margins.left; + } + if ("right" in obj) { + this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; + } + if ("top" in obj) { + this.offset.click.top = obj.top + this.margins.top; + } + if ("bottom" in obj) { + this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; + } + }, + + _getParentOffset: function() { + + + //Get the offsetParent and cache its position + this.offsetParent = this.helper.offsetParent(); + var po = this.offsetParent.offset(); + + // This is a special case where we need to modify a offset calculated on start, since the following happened: + // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent + // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that + // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag + if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { + po.left += this.scrollParent.scrollLeft(); + po.top += this.scrollParent.scrollTop(); + } + + // This needs to be actually done for all browsers, since pageX/pageY includes this information + // with an ugly IE fix + if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { + po = { top: 0, left: 0 }; + } + + return { + top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), + left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) + }; + + }, + + _getRelativeOffset: function() { + + if(this.cssPosition === "relative") { + var p = this.currentItem.position(); + return { + top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), + left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() + }; + } else { + return { top: 0, left: 0 }; + } + + }, + + _cacheMargins: function() { + this.margins = { + left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), + top: (parseInt(this.currentItem.css("marginTop"),10) || 0) + }; + }, + + _cacheHelperProportions: function() { + this.helperProportions = { + width: this.helper.outerWidth(), + height: this.helper.outerHeight() + }; + }, + + _setContainment: function() { + + var ce, co, over, + o = this.options; + if(o.containment === "parent") { + o.containment = this.helper[0].parentNode; + } + if(o.containment === "document" || o.containment === "window") { + this.containment = [ + 0 - this.offset.relative.left - this.offset.parent.left, + 0 - this.offset.relative.top - this.offset.parent.top, + $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, + ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top + ]; + } + + if(!(/^(document|window|parent)$/).test(o.containment)) { + ce = $(o.containment)[0]; + co = $(o.containment).offset(); + over = ($(ce).css("overflow") !== "hidden"); + + this.containment = [ + co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, + co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, + co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, + co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top + ]; + } + + }, + + _convertPositionTo: function(d, pos) { + + if(!pos) { + pos = this.position; + } + var mod = d === "absolute" ? 1 : -1, + scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, + scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + return { + top: ( + pos.top + // The absolute mouse position + this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) + ), + left: ( + pos.left + // The absolute mouse position + this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) + ) + }; + + }, + + _generatePosition: function(event) { + + var top, left, + o = this.options, + pageX = event.pageX, + pageY = event.pageY, + scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); + + // This is another very weird special case that only happens for relative elements: + // 1. If the css position is relative + // 2. and the scroll parent is the document or similar to the offset parent + // we have to refresh the relative offset during the scroll so there are no jumps + if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) { + this.offset.relative = this._getRelativeOffset(); + } + + /* + * - Position constraining - + * Constrain the position to a mix of grid, containment. + */ + + if(this.originalPosition) { //If we are not dragging yet, we won't check for options + + if(this.containment) { + if(event.pageX - this.offset.click.left < this.containment[0]) { + pageX = this.containment[0] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top < this.containment[1]) { + pageY = this.containment[1] + this.offset.click.top; + } + if(event.pageX - this.offset.click.left > this.containment[2]) { + pageX = this.containment[2] + this.offset.click.left; + } + if(event.pageY - this.offset.click.top > this.containment[3]) { + pageY = this.containment[3] + this.offset.click.top; + } + } + + if(o.grid) { + top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; + pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; + + left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; + pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; + } + + } + + return { + top: ( + pageY - // The absolute mouse position + this.offset.click.top - // Click offset (relative to the element) + this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.top + // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) + ), + left: ( + pageX - // The absolute mouse position + this.offset.click.left - // Click offset (relative to the element) + this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent + this.offset.parent.left + // The offsetParent's offset without borders (offset + border) + ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) + ) + }; + + }, + + _rearrange: function(event, i, a, hardRefresh) { + + a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling)); + + //Various things done here to improve the performance: + // 1. we create a setTimeout, that calls refreshPositions + // 2. on the instance, we have a counter variable, that get's higher after every append + // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same + // 4. this lets only the last addition to the timeout stack through + this.counter = this.counter ? ++this.counter : 1; + var counter = this.counter; + + this._delay(function() { + if(counter === this.counter) { + this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove + } + }); + + }, + + _clear: function(event, noPropagation) { + + this.reverting = false; + // We delay all events that have to be triggered to after the point where the placeholder has been removed and + // everything else normalized again + var i, + delayedTriggers = []; + + // We first have to update the dom position of the actual currentItem + // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) + if(!this._noFinalSort && this.currentItem.parent().length) { + this.placeholder.before(this.currentItem); + } + this._noFinalSort = null; + + if(this.helper[0] === this.currentItem[0]) { + for(i in this._storedCSS) { + if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") { + this._storedCSS[i] = ""; + } + } + this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); + } else { + this.currentItem.show(); + } + + if(this.fromOutside && !noPropagation) { + delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); + } + if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) { + delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed + } + + // Check if the items Container has Changed and trigger appropriate + // events. + if (this !== this.currentContainer) { + if(!noPropagation) { + delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); + delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); + delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); + } + } + + + //Post events to containers + for (i = this.containers.length - 1; i >= 0; i--){ + if(!noPropagation) { + delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + } + if(this.containers[i].containerCache.over) { + delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); + this.containers[i].containerCache.over = 0; + } + } + + //Do what was originally in plugins + if ( this.storedCursor ) { + this.document.find( "body" ).css( "cursor", this.storedCursor ); + this.storedStylesheet.remove(); + } + if(this._storedOpacity) { + this.helper.css("opacity", this._storedOpacity); + } + if(this._storedZIndex) { + this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex); + } + + this.dragging = false; + if(this.cancelHelperRemoval) { + if(!noPropagation) { + this._trigger("beforeStop", event, this._uiHash()); + for (i=0; i < delayedTriggers.length; i++) { + delayedTriggers[i].call(this, event); + } //Trigger all delayed events + this._trigger("stop", event, this._uiHash()); + } + + this.fromOutside = false; + return false; + } + + if(!noPropagation) { + this._trigger("beforeStop", event, this._uiHash()); + } + + //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! + this.placeholder[0].parentNode.removeChild(this.placeholder[0]); + + if(this.helper[0] !== this.currentItem[0]) { + this.helper.remove(); + } + this.helper = null; + + if(!noPropagation) { + for (i=0; i < delayedTriggers.length; i++) { + delayedTriggers[i].call(this, event); + } //Trigger all delayed events + this._trigger("stop", event, this._uiHash()); + } + + this.fromOutside = false; + return true; + + }, + + _trigger: function() { + if ($.Widget.prototype._trigger.apply(this, arguments) === false) { + this.cancel(); + } + }, + + _uiHash: function(_inst) { + var inst = _inst || this; + return { + helper: inst.helper, + placeholder: inst.placeholder || $([]), + position: inst.position, + originalPosition: inst.originalPosition, + offset: inst.positionAbs, + item: inst.currentItem, + sender: _inst ? _inst.element : null + }; + } + +}); + +})(jQuery); + +(function($, undefined) { + +var dataSpace = "ui-effects-"; + +$.effects = { + effect: {} +}; + +/*! + * jQuery Color Animations v2.1.2 + * https://github.com/jquery/jquery-color + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * Date: Wed Jan 16 08:47:09 2013 -0600 + */ +(function( jQuery, undefined ) { + + var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", + + // plusequals test for += 100 -= 100 + rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, + // a set of RE's that can match strings and generate color tuples. + stringParsers = [{ + re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + parse: function( execResult ) { + return [ + execResult[ 1 ], + execResult[ 2 ], + execResult[ 3 ], + execResult[ 4 ] + ]; + } + }, { + re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + parse: function( execResult ) { + return [ + execResult[ 1 ] * 2.55, + execResult[ 2 ] * 2.55, + execResult[ 3 ] * 2.55, + execResult[ 4 ] + ]; + } + }, { + // this regex ignores A-F because it's compared against an already lowercased string + re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/, + parse: function( execResult ) { + return [ + parseInt( execResult[ 1 ], 16 ), + parseInt( execResult[ 2 ], 16 ), + parseInt( execResult[ 3 ], 16 ) + ]; + } + }, { + // this regex ignores A-F because it's compared against an already lowercased string + re: /#([a-f0-9])([a-f0-9])([a-f0-9])/, + parse: function( execResult ) { + return [ + parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), + parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), + parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) + ]; + } + }, { + re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, + space: "hsla", + parse: function( execResult ) { + return [ + execResult[ 1 ], + execResult[ 2 ] / 100, + execResult[ 3 ] / 100, + execResult[ 4 ] + ]; + } + }], + + // jQuery.Color( ) + color = jQuery.Color = function( color, green, blue, alpha ) { + return new jQuery.Color.fn.parse( color, green, blue, alpha ); + }, + spaces = { + rgba: { + props: { + red: { + idx: 0, + type: "byte" + }, + green: { + idx: 1, + type: "byte" + }, + blue: { + idx: 2, + type: "byte" + } + } + }, + + hsla: { + props: { + hue: { + idx: 0, + type: "degrees" + }, + saturation: { + idx: 1, + type: "percent" + }, + lightness: { + idx: 2, + type: "percent" + } + } + } + }, + propTypes = { + "byte": { + floor: true, + max: 255 + }, + "percent": { + max: 1 + }, + "degrees": { + mod: 360, + floor: true + } + }, + support = color.support = {}, + + // element for support tests + supportElem = jQuery( "<p>" )[ 0 ], + + // colors = jQuery.Color.names + colors, + + // local aliases of functions called often + each = jQuery.each; + +// determine rgba support immediately +supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; +support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; + +// define cache name and alpha properties +// for rgba and hsla spaces +each( spaces, function( spaceName, space ) { + space.cache = "_" + spaceName; + space.props.alpha = { + idx: 3, + type: "percent", + def: 1 + }; +}); + +function clamp( value, prop, allowEmpty ) { + var type = propTypes[ prop.type ] || {}; + + if ( value == null ) { + return (allowEmpty || !prop.def) ? null : prop.def; + } + + // ~~ is an short way of doing floor for positive numbers + value = type.floor ? ~~value : parseFloat( value ); + + // IE will pass in empty strings as value for alpha, + // which will hit this case + if ( isNaN( value ) ) { + return prop.def; + } + + if ( type.mod ) { + // we add mod before modding to make sure that negatives values + // get converted properly: -10 -> 350 + return (value + type.mod) % type.mod; + } + + // for now all property types without mod have min and max + return 0 > value ? 0 : type.max < value ? type.max : value; +} + +function stringParse( string ) { + var inst = color(), + rgba = inst._rgba = []; + + string = string.toLowerCase(); + + each( stringParsers, function( i, parser ) { + var parsed, + match = parser.re.exec( string ), + values = match && parser.parse( match ), + spaceName = parser.space || "rgba"; + + if ( values ) { + parsed = inst[ spaceName ]( values ); + + // if this was an rgba parse the assignment might happen twice + // oh well.... + inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; + rgba = inst._rgba = parsed._rgba; + + // exit each( stringParsers ) here because we matched + return false; + } + }); + + // Found a stringParser that handled it + if ( rgba.length ) { + + // if this came from a parsed string, force "transparent" when alpha is 0 + // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) + if ( rgba.join() === "0,0,0,0" ) { + jQuery.extend( rgba, colors.transparent ); + } + return inst; + } + + // named colors + return colors[ string ]; +} + +color.fn = jQuery.extend( color.prototype, { + parse: function( red, green, blue, alpha ) { + if ( red === undefined ) { + this._rgba = [ null, null, null, null ]; + return this; + } + if ( red.jquery || red.nodeType ) { + red = jQuery( red ).css( green ); + green = undefined; + } + + var inst = this, + type = jQuery.type( red ), + rgba = this._rgba = []; + + // more than 1 argument specified - assume ( red, green, blue, alpha ) + if ( green !== undefined ) { + red = [ red, green, blue, alpha ]; + type = "array"; + } + + if ( type === "string" ) { + return this.parse( stringParse( red ) || colors._default ); + } + + if ( type === "array" ) { + each( spaces.rgba.props, function( key, prop ) { + rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); + }); + return this; + } + + if ( type === "object" ) { + if ( red instanceof color ) { + each( spaces, function( spaceName, space ) { + if ( red[ space.cache ] ) { + inst[ space.cache ] = red[ space.cache ].slice(); + } + }); + } else { + each( spaces, function( spaceName, space ) { + var cache = space.cache; + each( space.props, function( key, prop ) { + + // if the cache doesn't exist, and we know how to convert + if ( !inst[ cache ] && space.to ) { + + // if the value was null, we don't need to copy it + // if the key was alpha, we don't need to copy it either + if ( key === "alpha" || red[ key ] == null ) { + return; + } + inst[ cache ] = space.to( inst._rgba ); + } + + // this is the only case where we allow nulls for ALL properties. + // call clamp with alwaysAllowEmpty + inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); + }); + + // everything defined but alpha? + if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { + // use the default of 1 + inst[ cache ][ 3 ] = 1; + if ( space.from ) { + inst._rgba = space.from( inst[ cache ] ); + } + } + }); + } + return this; + } + }, + is: function( compare ) { + var is = color( compare ), + same = true, + inst = this; + + each( spaces, function( _, space ) { + var localCache, + isCache = is[ space.cache ]; + if (isCache) { + localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; + each( space.props, function( _, prop ) { + if ( isCache[ prop.idx ] != null ) { + same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); + return same; + } + }); + } + return same; + }); + return same; + }, + _space: function() { + var used = [], + inst = this; + each( spaces, function( spaceName, space ) { + if ( inst[ space.cache ] ) { + used.push( spaceName ); + } + }); + return used.pop(); + }, + transition: function( other, distance ) { + var end = color( other ), + spaceName = end._space(), + space = spaces[ spaceName ], + startColor = this.alpha() === 0 ? color( "transparent" ) : this, + start = startColor[ space.cache ] || space.to( startColor._rgba ), + result = start.slice(); + + end = end[ space.cache ]; + each( space.props, function( key, prop ) { + var index = prop.idx, + startValue = start[ index ], + endValue = end[ index ], + type = propTypes[ prop.type ] || {}; + + // if null, don't override start value + if ( endValue === null ) { + return; + } + // if null - use end + if ( startValue === null ) { + result[ index ] = endValue; + } else { + if ( type.mod ) { + if ( endValue - startValue > type.mod / 2 ) { + startValue += type.mod; + } else if ( startValue - endValue > type.mod / 2 ) { + startValue -= type.mod; + } + } + result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); + } + }); + return this[ spaceName ]( result ); + }, + blend: function( opaque ) { + // if we are already opaque - return ourself + if ( this._rgba[ 3 ] === 1 ) { + return this; + } + + var rgb = this._rgba.slice(), + a = rgb.pop(), + blend = color( opaque )._rgba; + + return color( jQuery.map( rgb, function( v, i ) { + return ( 1 - a ) * blend[ i ] + a * v; + })); + }, + toRgbaString: function() { + var prefix = "rgba(", + rgba = jQuery.map( this._rgba, function( v, i ) { + return v == null ? ( i > 2 ? 1 : 0 ) : v; + }); + + if ( rgba[ 3 ] === 1 ) { + rgba.pop(); + prefix = "rgb("; + } + + return prefix + rgba.join() + ")"; + }, + toHslaString: function() { + var prefix = "hsla(", + hsla = jQuery.map( this.hsla(), function( v, i ) { + if ( v == null ) { + v = i > 2 ? 1 : 0; + } + + // catch 1 and 2 + if ( i && i < 3 ) { + v = Math.round( v * 100 ) + "%"; + } + return v; + }); + + if ( hsla[ 3 ] === 1 ) { + hsla.pop(); + prefix = "hsl("; + } + return prefix + hsla.join() + ")"; + }, + toHexString: function( includeAlpha ) { + var rgba = this._rgba.slice(), + alpha = rgba.pop(); + + if ( includeAlpha ) { + rgba.push( ~~( alpha * 255 ) ); + } + + return "#" + jQuery.map( rgba, function( v ) { + + // default to 0 when nulls exist + v = ( v || 0 ).toString( 16 ); + return v.length === 1 ? "0" + v : v; + }).join(""); + }, + toString: function() { + return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); + } +}); +color.fn.parse.prototype = color.fn; + +// hsla conversions adapted from: +// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 + +function hue2rgb( p, q, h ) { + h = ( h + 1 ) % 1; + if ( h * 6 < 1 ) { + return p + (q - p) * h * 6; + } + if ( h * 2 < 1) { + return q; + } + if ( h * 3 < 2 ) { + return p + (q - p) * ((2/3) - h) * 6; + } + return p; +} + +spaces.hsla.to = function ( rgba ) { + if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { + return [ null, null, null, rgba[ 3 ] ]; + } + var r = rgba[ 0 ] / 255, + g = rgba[ 1 ] / 255, + b = rgba[ 2 ] / 255, + a = rgba[ 3 ], + max = Math.max( r, g, b ), + min = Math.min( r, g, b ), + diff = max - min, + add = max + min, + l = add * 0.5, + h, s; + + if ( min === max ) { + h = 0; + } else if ( r === max ) { + h = ( 60 * ( g - b ) / diff ) + 360; + } else if ( g === max ) { + h = ( 60 * ( b - r ) / diff ) + 120; + } else { + h = ( 60 * ( r - g ) / diff ) + 240; + } + + // chroma (diff) == 0 means greyscale which, by definition, saturation = 0% + // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) + if ( diff === 0 ) { + s = 0; + } else if ( l <= 0.5 ) { + s = diff / add; + } else { + s = diff / ( 2 - add ); + } + return [ Math.round(h) % 360, s, l, a == null ? 1 : a ]; +}; + +spaces.hsla.from = function ( hsla ) { + if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { + return [ null, null, null, hsla[ 3 ] ]; + } + var h = hsla[ 0 ] / 360, + s = hsla[ 1 ], + l = hsla[ 2 ], + a = hsla[ 3 ], + q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, + p = 2 * l - q; + + return [ + Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), + Math.round( hue2rgb( p, q, h ) * 255 ), + Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), + a + ]; +}; + + +each( spaces, function( spaceName, space ) { + var props = space.props, + cache = space.cache, + to = space.to, + from = space.from; + + // makes rgba() and hsla() + color.fn[ spaceName ] = function( value ) { + + // generate a cache for this space if it doesn't exist + if ( to && !this[ cache ] ) { + this[ cache ] = to( this._rgba ); + } + if ( value === undefined ) { + return this[ cache ].slice(); + } + + var ret, + type = jQuery.type( value ), + arr = ( type === "array" || type === "object" ) ? value : arguments, + local = this[ cache ].slice(); + + each( props, function( key, prop ) { + var val = arr[ type === "object" ? key : prop.idx ]; + if ( val == null ) { + val = local[ prop.idx ]; + } + local[ prop.idx ] = clamp( val, prop ); + }); + + if ( from ) { + ret = color( from( local ) ); + ret[ cache ] = local; + return ret; + } else { + return color( local ); + } + }; + + // makes red() green() blue() alpha() hue() saturation() lightness() + each( props, function( key, prop ) { + // alpha is included in more than one space + if ( color.fn[ key ] ) { + return; + } + color.fn[ key ] = function( value ) { + var vtype = jQuery.type( value ), + fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ), + local = this[ fn ](), + cur = local[ prop.idx ], + match; + + if ( vtype === "undefined" ) { + return cur; + } + + if ( vtype === "function" ) { + value = value.call( this, cur ); + vtype = jQuery.type( value ); + } + if ( value == null && prop.empty ) { + return this; + } + if ( vtype === "string" ) { + match = rplusequals.exec( value ); + if ( match ) { + value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); + } + } + local[ prop.idx ] = value; + return this[ fn ]( local ); + }; + }); +}); + +// add cssHook and .fx.step function for each named hook. +// accept a space separated string of properties +color.hook = function( hook ) { + var hooks = hook.split( " " ); + each( hooks, function( i, hook ) { + jQuery.cssHooks[ hook ] = { + set: function( elem, value ) { + var parsed, curElem, + backgroundColor = ""; + + if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) { + value = color( parsed || value ); + if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { + curElem = hook === "backgroundColor" ? elem.parentNode : elem; + while ( + (backgroundColor === "" || backgroundColor === "transparent") && + curElem && curElem.style + ) { + try { + backgroundColor = jQuery.css( curElem, "backgroundColor" ); + curElem = curElem.parentNode; + } catch ( e ) { + } + } + + value = value.blend( backgroundColor && backgroundColor !== "transparent" ? + backgroundColor : + "_default" ); + } + + value = value.toRgbaString(); + } + try { + elem.style[ hook ] = value; + } catch( e ) { + // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit' + } + } + }; + jQuery.fx.step[ hook ] = function( fx ) { + if ( !fx.colorInit ) { + fx.start = color( fx.elem, hook ); + fx.end = color( fx.end ); + fx.colorInit = true; + } + jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); + }; + }); + +}; + +color.hook( stepHooks ); + +jQuery.cssHooks.borderColor = { + expand: function( value ) { + var expanded = {}; + + each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) { + expanded[ "border" + part + "Color" ] = value; + }); + return expanded; + } +}; + +// Basic color names only. +// Usage of any of the other color names requires adding yourself or including +// jquery.color.svg-names.js. +colors = jQuery.Color.names = { + // 4.1. Basic color keywords + aqua: "#00ffff", + black: "#000000", + blue: "#0000ff", + fuchsia: "#ff00ff", + gray: "#808080", + green: "#008000", + lime: "#00ff00", + maroon: "#800000", + navy: "#000080", + olive: "#808000", + purple: "#800080", + red: "#ff0000", + silver: "#c0c0c0", + teal: "#008080", + white: "#ffffff", + yellow: "#ffff00", + + // 4.2.3. "transparent" color keyword + transparent: [ null, null, null, 0 ], + + _default: "#ffffff" +}; + +})( jQuery ); + + +/******************************************************************************/ +/****************************** CLASS ANIMATIONS ******************************/ +/******************************************************************************/ +(function() { + +var classAnimationActions = [ "add", "remove", "toggle" ], + shorthandStyles = { + border: 1, + borderBottom: 1, + borderColor: 1, + borderLeft: 1, + borderRight: 1, + borderTop: 1, + borderWidth: 1, + margin: 1, + padding: 1 + }; + +$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) { + $.fx.step[ prop ] = function( fx ) { + if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { + jQuery.style( fx.elem, prop, fx.end ); + fx.setAttr = true; + } + }; +}); + +function getElementStyles( elem ) { + var key, len, + style = elem.ownerDocument.defaultView ? + elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : + elem.currentStyle, + styles = {}; + + if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { + len = style.length; + while ( len-- ) { + key = style[ len ]; + if ( typeof style[ key ] === "string" ) { + styles[ $.camelCase( key ) ] = style[ key ]; + } + } + // support: Opera, IE <9 + } else { + for ( key in style ) { + if ( typeof style[ key ] === "string" ) { + styles[ key ] = style[ key ]; + } + } + } + + return styles; +} + + +function styleDifference( oldStyle, newStyle ) { + var diff = {}, + name, value; + + for ( name in newStyle ) { + value = newStyle[ name ]; + if ( oldStyle[ name ] !== value ) { + if ( !shorthandStyles[ name ] ) { + if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) { + diff[ name ] = value; + } + } + } + } + + return diff; +} + +// support: jQuery <1.8 +if ( !$.fn.addBack ) { + $.fn.addBack = function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + }; +} + +$.effects.animateClass = function( value, duration, easing, callback ) { + var o = $.speed( duration, easing, callback ); + + return this.queue( function() { + var animated = $( this ), + baseClass = animated.attr( "class" ) || "", + applyClassChange, + allAnimations = o.children ? animated.find( "*" ).addBack() : animated; + + // map the animated objects to store the original styles. + allAnimations = allAnimations.map(function() { + var el = $( this ); + return { + el: el, + start: getElementStyles( this ) + }; + }); + + // apply class change + applyClassChange = function() { + $.each( classAnimationActions, function(i, action) { + if ( value[ action ] ) { + animated[ action + "Class" ]( value[ action ] ); + } + }); + }; + applyClassChange(); + + // map all animated objects again - calculate new styles and diff + allAnimations = allAnimations.map(function() { + this.end = getElementStyles( this.el[ 0 ] ); + this.diff = styleDifference( this.start, this.end ); + return this; + }); + + // apply original class + animated.attr( "class", baseClass ); + + // map all animated objects again - this time collecting a promise + allAnimations = allAnimations.map(function() { + var styleInfo = this, + dfd = $.Deferred(), + opts = $.extend({}, o, { + queue: false, + complete: function() { + dfd.resolve( styleInfo ); + } + }); + + this.el.animate( this.diff, opts ); + return dfd.promise(); + }); + + // once all animations have completed: + $.when.apply( $, allAnimations.get() ).done(function() { + + // set the final class + applyClassChange(); + + // for each animated element, + // clear all css properties that were animated + $.each( arguments, function() { + var el = this.el; + $.each( this.diff, function(key) { + el.css( key, "" ); + }); + }); + + // this is guarnteed to be there if you use jQuery.speed() + // it also handles dequeuing the next anim... + o.complete.call( animated[ 0 ] ); + }); + }); +}; + +$.fn.extend({ + addClass: (function( orig ) { + return function( classNames, speed, easing, callback ) { + return speed ? + $.effects.animateClass.call( this, + { add: classNames }, speed, easing, callback ) : + orig.apply( this, arguments ); + }; + })( $.fn.addClass ), + + removeClass: (function( orig ) { + return function( classNames, speed, easing, callback ) { + return arguments.length > 1 ? + $.effects.animateClass.call( this, + { remove: classNames }, speed, easing, callback ) : + orig.apply( this, arguments ); + }; + })( $.fn.removeClass ), + + toggleClass: (function( orig ) { + return function( classNames, force, speed, easing, callback ) { + if ( typeof force === "boolean" || force === undefined ) { + if ( !speed ) { + // without speed parameter + return orig.apply( this, arguments ); + } else { + return $.effects.animateClass.call( this, + (force ? { add: classNames } : { remove: classNames }), + speed, easing, callback ); + } + } else { + // without force parameter + return $.effects.animateClass.call( this, + { toggle: classNames }, force, speed, easing ); + } + }; + })( $.fn.toggleClass ), + + switchClass: function( remove, add, speed, easing, callback) { + return $.effects.animateClass.call( this, { + add: add, + remove: remove + }, speed, easing, callback ); + } +}); + +})(); + +/******************************************************************************/ +/*********************************** EFFECTS **********************************/ +/******************************************************************************/ + +(function() { + +$.extend( $.effects, { + version: "1.10.2", + + // Saves a set of properties in a data storage + save: function( element, set ) { + for( var i=0; i < set.length; i++ ) { + if ( set[ i ] !== null ) { + element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); + } + } + }, + + // Restores a set of previously saved properties from a data storage + restore: function( element, set ) { + var val, i; + for( i=0; i < set.length; i++ ) { + if ( set[ i ] !== null ) { + val = element.data( dataSpace + set[ i ] ); + // support: jQuery 1.6.2 + // http://bugs.jquery.com/ticket/9917 + // jQuery 1.6.2 incorrectly returns undefined for any falsy value. + // We can't differentiate between "" and 0 here, so we just assume + // empty string since it's likely to be a more common value... + if ( val === undefined ) { + val = ""; + } + element.css( set[ i ], val ); + } + } + }, + + setMode: function( el, mode ) { + if (mode === "toggle") { + mode = el.is( ":hidden" ) ? "show" : "hide"; + } + return mode; + }, + + // Translates a [top,left] array into a baseline value + // this should be a little more flexible in the future to handle a string & hash + getBaseline: function( origin, original ) { + var y, x; + switch ( origin[ 0 ] ) { + case "top": y = 0; break; + case "middle": y = 0.5; break; + case "bottom": y = 1; break; + default: y = origin[ 0 ] / original.height; + } + switch ( origin[ 1 ] ) { + case "left": x = 0; break; + case "center": x = 0.5; break; + case "right": x = 1; break; + default: x = origin[ 1 ] / original.width; + } + return { + x: x, + y: y + }; + }, + + // Wraps the element around a wrapper that copies position properties + createWrapper: function( element ) { + + // if the element is already wrapped, return it + if ( element.parent().is( ".ui-effects-wrapper" )) { + return element.parent(); + } + + // wrap the element + var props = { + width: element.outerWidth(true), + height: element.outerHeight(true), + "float": element.css( "float" ) + }, + wrapper = $( "<div></div>" ) + .addClass( "ui-effects-wrapper" ) + .css({ + fontSize: "100%", + background: "transparent", + border: "none", + margin: 0, + padding: 0 + }), + // Store the size in case width/height are defined in % - Fixes #5245 + size = { + width: element.width(), + height: element.height() + }, + active = document.activeElement; + + // support: Firefox + // Firefox incorrectly exposes anonymous content + // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 + try { + active.id; + } catch( e ) { + active = document.body; + } + + element.wrap( wrapper ); + + // Fixes #7595 - Elements lose focus when wrapped. + if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { + $( active ).focus(); + } + + wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element + + // transfer positioning properties to the wrapper + if ( element.css( "position" ) === "static" ) { + wrapper.css({ position: "relative" }); + element.css({ position: "relative" }); + } else { + $.extend( props, { + position: element.css( "position" ), + zIndex: element.css( "z-index" ) + }); + $.each([ "top", "left", "bottom", "right" ], function(i, pos) { + props[ pos ] = element.css( pos ); + if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { + props[ pos ] = "auto"; + } + }); + element.css({ + position: "relative", + top: 0, + left: 0, + right: "auto", + bottom: "auto" + }); + } + element.css(size); + + return wrapper.css( props ).show(); + }, + + removeWrapper: function( element ) { + var active = document.activeElement; + + if ( element.parent().is( ".ui-effects-wrapper" ) ) { + element.parent().replaceWith( element ); + + // Fixes #7595 - Elements lose focus when wrapped. + if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { + $( active ).focus(); + } + } + + + return element; + }, + + setTransition: function( element, list, factor, value ) { + value = value || {}; + $.each( list, function( i, x ) { + var unit = element.cssUnit( x ); + if ( unit[ 0 ] > 0 ) { + value[ x ] = unit[ 0 ] * factor + unit[ 1 ]; + } + }); + return value; + } +}); + +// return an effect options object for the given parameters: +function _normalizeArguments( effect, options, speed, callback ) { + + // allow passing all options as the first parameter + if ( $.isPlainObject( effect ) ) { + options = effect; + effect = effect.effect; + } + + // convert to an object + effect = { effect: effect }; + + // catch (effect, null, ...) + if ( options == null ) { + options = {}; + } + + // catch (effect, callback) + if ( $.isFunction( options ) ) { + callback = options; + speed = null; + options = {}; + } + + // catch (effect, speed, ?) + if ( typeof options === "number" || $.fx.speeds[ options ] ) { + callback = speed; + speed = options; + options = {}; + } + + // catch (effect, options, callback) + if ( $.isFunction( speed ) ) { + callback = speed; + speed = null; + } + + // add options to effect + if ( options ) { + $.extend( effect, options ); + } + + speed = speed || options.duration; + effect.duration = $.fx.off ? 0 : + typeof speed === "number" ? speed : + speed in $.fx.speeds ? $.fx.speeds[ speed ] : + $.fx.speeds._default; + + effect.complete = callback || options.complete; + + return effect; +} + +function standardAnimationOption( option ) { + // Valid standard speeds (nothing, number, named speed) + if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) { + return true; + } + + // Invalid strings - treat as "normal" speed + if ( typeof option === "string" && !$.effects.effect[ option ] ) { + return true; + } + + // Complete callback + if ( $.isFunction( option ) ) { + return true; + } + + // Options hash (but not naming an effect) + if ( typeof option === "object" && !option.effect ) { + return true; + } + + // Didn't match any standard API + return false; +} + +$.fn.extend({ + effect: function( /* effect, options, speed, callback */ ) { + var args = _normalizeArguments.apply( this, arguments ), + mode = args.mode, + queue = args.queue, + effectMethod = $.effects.effect[ args.effect ]; + + if ( $.fx.off || !effectMethod ) { + // delegate to the original method (e.g., .show()) if possible + if ( mode ) { + return this[ mode ]( args.duration, args.complete ); + } else { + return this.each( function() { + if ( args.complete ) { + args.complete.call( this ); + } + }); + } + } + + function run( next ) { + var elem = $( this ), + complete = args.complete, + mode = args.mode; + + function done() { + if ( $.isFunction( complete ) ) { + complete.call( elem[0] ); + } + if ( $.isFunction( next ) ) { + next(); + } + } + + // If the element already has the correct final state, delegate to + // the core methods so the internal tracking of "olddisplay" works. + if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { + elem[ mode ](); + done(); + } else { + effectMethod.call( elem[0], args, done ); + } + } + + return queue === false ? this.each( run ) : this.queue( queue || "fx", run ); + }, + + show: (function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "show"; + return this.effect.call( this, args ); + } + }; + })( $.fn.show ), + + hide: (function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "hide"; + return this.effect.call( this, args ); + } + }; + })( $.fn.hide ), + + toggle: (function( orig ) { + return function( option ) { + if ( standardAnimationOption( option ) || typeof option === "boolean" ) { + return orig.apply( this, arguments ); + } else { + var args = _normalizeArguments.apply( this, arguments ); + args.mode = "toggle"; + return this.effect.call( this, args ); + } + }; + })( $.fn.toggle ), + + // helper functions + cssUnit: function(key) { + var style = this.css( key ), + val = []; + + $.each( [ "em", "px", "%", "pt" ], function( i, unit ) { + if ( style.indexOf( unit ) > 0 ) { + val = [ parseFloat( style ), unit ]; + } + }); + return val; + } +}); + +})(); + +/******************************************************************************/ +/*********************************** EASING ***********************************/ +/******************************************************************************/ + +(function() { + +// based on easing equations from Robert Penner (http://www.robertpenner.com/easing) + +var baseEasings = {}; + +$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { + baseEasings[ name ] = function( p ) { + return Math.pow( p, i + 2 ); + }; +}); + +$.extend( baseEasings, { + Sine: function ( p ) { + return 1 - Math.cos( p * Math.PI / 2 ); + }, + Circ: function ( p ) { + return 1 - Math.sqrt( 1 - p * p ); + }, + Elastic: function( p ) { + return p === 0 || p === 1 ? p : + -Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 ); + }, + Back: function( p ) { + return p * p * ( 3 * p - 2 ); + }, + Bounce: function ( p ) { + var pow2, + bounce = 4; + + while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} + return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); + } +}); + +$.each( baseEasings, function( name, easeIn ) { + $.easing[ "easeIn" + name ] = easeIn; + $.easing[ "easeOut" + name ] = function( p ) { + return 1 - easeIn( 1 - p ); + }; + $.easing[ "easeInOut" + name ] = function( p ) { + return p < 0.5 ? + easeIn( p * 2 ) / 2 : + 1 - easeIn( p * -2 + 2 ) / 2; + }; +}); + +})(); + +})(jQuery); + +(function( $, undefined ) { + +var uid = 0, + hideProps = {}, + showProps = {}; + +hideProps.height = hideProps.paddingTop = hideProps.paddingBottom = + hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide"; +showProps.height = showProps.paddingTop = showProps.paddingBottom = + showProps.borderTopWidth = showProps.borderBottomWidth = "show"; + +$.widget( "ui.accordion", { + version: "1.10.2", + options: { + active: 0, + animate: {}, + collapsible: false, + event: "click", + header: "> li > :first-child,> :not(li):even", + heightStyle: "auto", + icons: { + activeHeader: "ui-icon-triangle-1-s", + header: "ui-icon-triangle-1-e" + }, + + // callbacks + activate: null, + beforeActivate: null + }, + + _create: function() { + var options = this.options; + this.prevShow = this.prevHide = $(); + this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) + // ARIA + .attr( "role", "tablist" ); + + // don't allow collapsible: false and active: false / null + if ( !options.collapsible && (options.active === false || options.active == null) ) { + options.active = 0; + } + + this._processPanels(); + // handle negative values + if ( options.active < 0 ) { + options.active += this.headers.length; + } + this._refresh(); + }, + + _getCreateEventData: function() { + return { + header: this.active, + panel: !this.active.length ? $() : this.active.next(), + content: !this.active.length ? $() : this.active.next() + }; + }, + + _createIcons: function() { + var icons = this.options.icons; + if ( icons ) { + $( "<span>" ) + .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) + .prependTo( this.headers ); + this.active.children( ".ui-accordion-header-icon" ) + .removeClass( icons.header ) + .addClass( icons.activeHeader ); + this.headers.addClass( "ui-accordion-icons" ); + } + }, + + _destroyIcons: function() { + this.headers + .removeClass( "ui-accordion-icons" ) + .children( ".ui-accordion-header-icon" ) + .remove(); + }, + + _destroy: function() { + var contents; + + // clean up main element + this.element + .removeClass( "ui-accordion ui-widget ui-helper-reset" ) + .removeAttr( "role" ); + + // clean up headers + this.headers + .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) + .removeAttr( "role" ) + .removeAttr( "aria-selected" ) + .removeAttr( "aria-controls" ) + .removeAttr( "tabIndex" ) + .each(function() { + if ( /^ui-accordion/.test( this.id ) ) { + this.removeAttribute( "id" ); + } + }); + this._destroyIcons(); + + // clean up content panels + contents = this.headers.next() + .css( "display", "" ) + .removeAttr( "role" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-labelledby" ) + .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" ) + .each(function() { + if ( /^ui-accordion/.test( this.id ) ) { + this.removeAttribute( "id" ); + } + }); + if ( this.options.heightStyle !== "content" ) { + contents.css( "height", "" ); + } + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + if ( key === "event" ) { + if ( this.options.event ) { + this._off( this.headers, this.options.event ); + } + this._setupEvents( value ); + } + + this._super( key, value ); + + // setting collapsible: false while collapsed; open first panel + if ( key === "collapsible" && !value && this.options.active === false ) { + this._activate( 0 ); + } + + if ( key === "icons" ) { + this._destroyIcons(); + if ( value ) { + this._createIcons(); + } + } + + // #5332 - opacity doesn't cascade to positioned elements in IE + // so we need to add the disabled class to the headers and panels + if ( key === "disabled" ) { + this.headers.add( this.headers.next() ) + .toggleClass( "ui-state-disabled", !!value ); + } + }, + + _keydown: function( event ) { + /*jshint maxcomplexity:15*/ + if ( event.altKey || event.ctrlKey ) { + return; + } + + var keyCode = $.ui.keyCode, + length = this.headers.length, + currentIndex = this.headers.index( event.target ), + toFocus = false; + + switch ( event.keyCode ) { + case keyCode.RIGHT: + case keyCode.DOWN: + toFocus = this.headers[ ( currentIndex + 1 ) % length ]; + break; + case keyCode.LEFT: + case keyCode.UP: + toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; + break; + case keyCode.SPACE: + case keyCode.ENTER: + this._eventHandler( event ); + break; + case keyCode.HOME: + toFocus = this.headers[ 0 ]; + break; + case keyCode.END: + toFocus = this.headers[ length - 1 ]; + break; + } + + if ( toFocus ) { + $( event.target ).attr( "tabIndex", -1 ); + $( toFocus ).attr( "tabIndex", 0 ); + toFocus.focus(); + event.preventDefault(); + } + }, + + _panelKeyDown : function( event ) { + if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { + $( event.currentTarget ).prev().focus(); + } + }, + + refresh: function() { + var options = this.options; + this._processPanels(); + + // was collapsed or no panel + if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { + options.active = false; + this.active = $(); + // active false only when collapsible is true + } if ( options.active === false ) { + this._activate( 0 ); + // was active, but active panel is gone + } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + // all remaining panel are disabled + if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { + options.active = false; + this.active = $(); + // activate previous panel + } else { + this._activate( Math.max( 0, options.active - 1 ) ); + } + // was active, active panel still exists + } else { + // make sure active index is correct + options.active = this.headers.index( this.active ); + } + + this._destroyIcons(); + + this._refresh(); + }, + + _processPanels: function() { + this.headers = this.element.find( this.options.header ) + .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ); + + this.headers.next() + .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) + .filter(":not(.ui-accordion-content-active)") + .hide(); + }, + + _refresh: function() { + var maxHeight, + options = this.options, + heightStyle = options.heightStyle, + parent = this.element.parent(), + accordionId = this.accordionId = "ui-accordion-" + + (this.element.attr( "id" ) || ++uid); + + this.active = this._findActive( options.active ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) + .removeClass( "ui-corner-all" ); + this.active.next() + .addClass( "ui-accordion-content-active" ) + .show(); + + this.headers + .attr( "role", "tab" ) + .each(function( i ) { + var header = $( this ), + headerId = header.attr( "id" ), + panel = header.next(), + panelId = panel.attr( "id" ); + if ( !headerId ) { + headerId = accordionId + "-header-" + i; + header.attr( "id", headerId ); + } + if ( !panelId ) { + panelId = accordionId + "-panel-" + i; + panel.attr( "id", panelId ); + } + header.attr( "aria-controls", panelId ); + panel.attr( "aria-labelledby", headerId ); + }) + .next() + .attr( "role", "tabpanel" ); + + this.headers + .not( this.active ) + .attr({ + "aria-selected": "false", + tabIndex: -1 + }) + .next() + .attr({ + "aria-expanded": "false", + "aria-hidden": "true" + }) + .hide(); + + // make sure at least one header is in the tab order + if ( !this.active.length ) { + this.headers.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active.attr({ + "aria-selected": "true", + tabIndex: 0 + }) + .next() + .attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }); + } + + this._createIcons(); + + this._setupEvents( options.event ); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + this.element.siblings( ":visible" ).each(function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + }); + + this.headers.each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); + + this.headers.next() + .each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.headers.next() + .each(function() { + maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); + }) + .height( maxHeight ); + } + }, + + _activate: function( index ) { + var active = this._findActive( index )[ 0 ]; + + // trying to activate the already active panel + if ( active === this.active[ 0 ] ) { + return; + } + + // trying to collapse, simulate a click on the currently active header + active = active || this.active[ 0 ]; + + this._eventHandler({ + target: active, + currentTarget: active, + preventDefault: $.noop + }); + }, + + _findActive: function( selector ) { + return typeof selector === "number" ? this.headers.eq( selector ) : $(); + }, + + _setupEvents: function( event ) { + var events = { + keydown: "_keydown" + }; + if ( event ) { + $.each( event.split(" "), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); + } + + this._off( this.headers.add( this.headers.next() ) ); + this._on( this.headers, events ); + this._on( this.headers.next(), { keydown: "_panelKeyDown" }); + this._hoverable( this.headers ); + this._focusable( this.headers ); + }, + + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + clicked = $( event.currentTarget ), + clickedIsActive = clicked[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : clicked.next(), + toHide = active.next(), + eventData = { + oldHeader: active, + oldPanel: toHide, + newHeader: collapsing ? $() : clicked, + newPanel: toShow + }; + + event.preventDefault(); + + if ( + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.headers.index( clicked ); + + // when the call to ._toggle() comes after the class changes + // it causes a very odd bug in IE 8 (see #6720) + this.active = clickedIsActive ? $() : clicked; + this._toggle( eventData ); + + // switch classes + // corner classes on the previously active header stay after the animation + active.removeClass( "ui-accordion-header-active ui-state-active" ); + if ( options.icons ) { + active.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.activeHeader ) + .addClass( options.icons.header ); + } + + if ( !clickedIsActive ) { + clicked + .removeClass( "ui-corner-all" ) + .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); + if ( options.icons ) { + clicked.children( ".ui-accordion-header-icon" ) + .removeClass( options.icons.header ) + .addClass( options.icons.activeHeader ); + } + + clicked + .next() + .addClass( "ui-accordion-content-active" ); + } + }, + + _toggle: function( data ) { + var toShow = data.newPanel, + toHide = this.prevShow.length ? this.prevShow : data.oldPanel; + + // handle activating a panel during the animation for another activation + this.prevShow.add( this.prevHide ).stop( true, true ); + this.prevShow = toShow; + this.prevHide = toHide; + + if ( this.options.animate ) { + this._animate( toShow, toHide, data ); + } else { + toHide.hide(); + toShow.show(); + this._toggleComplete( data ); + } + + toHide.attr({ + "aria-expanded": "false", + "aria-hidden": "true" + }); + toHide.prev().attr( "aria-selected", "false" ); + // if we're switching panels, remove the old header from the tab order + // if we're opening from collapsed state, remove the previous header from the tab order + // if we're collapsing, then keep the collapsing header in the tab order + if ( toShow.length && toHide.length ) { + toHide.prev().attr( "tabIndex", -1 ); + } else if ( toShow.length ) { + this.headers.filter(function() { + return $( this ).attr( "tabIndex" ) === 0; + }) + .attr( "tabIndex", -1 ); + } + + toShow + .attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }) + .prev() + .attr({ + "aria-selected": "true", + tabIndex: 0 + }); + }, + + _animate: function( toShow, toHide, data ) { + var total, easing, duration, + that = this, + adjust = 0, + down = toShow.length && + ( !toHide.length || ( toShow.index() < toHide.index() ) ), + animate = this.options.animate || {}, + options = down && animate.down || animate, + complete = function() { + that._toggleComplete( data ); + }; + + if ( typeof options === "number" ) { + duration = options; + } + if ( typeof options === "string" ) { + easing = options; + } + // fall back from options to animation in case of partial down settings + easing = easing || options.easing || animate.easing; + duration = duration || options.duration || animate.duration; + + if ( !toHide.length ) { + return toShow.animate( showProps, duration, easing, complete ); + } + if ( !toShow.length ) { + return toHide.animate( hideProps, duration, easing, complete ); + } + + total = toShow.show().outerHeight(); + toHide.animate( hideProps, { + duration: duration, + easing: easing, + step: function( now, fx ) { + fx.now = Math.round( now ); + } + }); + toShow + .hide() + .animate( showProps, { + duration: duration, + easing: easing, + complete: complete, + step: function( now, fx ) { + fx.now = Math.round( now ); + if ( fx.prop !== "height" ) { + adjust += fx.now; + } else if ( that.options.heightStyle !== "content" ) { + fx.now = Math.round( total - toHide.outerHeight() - adjust ); + adjust = 0; + } + } + }); + }, + + _toggleComplete: function( data ) { + var toHide = data.oldPanel; + + toHide + .removeClass( "ui-accordion-content-active" ) + .prev() + .removeClass( "ui-corner-top" ) + .addClass( "ui-corner-all" ); + + // Work around for rendering bug in IE (#5421) + if ( toHide.length ) { + toHide.parent()[0].className = toHide.parent()[0].className; + } + + this._trigger( "activate", null, data ); + } +}); + +})( jQuery ); + +(function( $, undefined ) { + +// used to prevent race conditions with remote data sources +var requestIndex = 0; + +$.widget( "ui.autocomplete", { + version: "1.10.2", + defaultElement: "<input>", + options: { + appendTo: null, + autoFocus: false, + delay: 300, + minLength: 1, + position: { + my: "left top", + at: "left bottom", + collision: "none" + }, + source: null, + + // callbacks + change: null, + close: null, + focus: null, + open: null, + response: null, + search: null, + select: null + }, + + pending: 0, + + _create: function() { + // Some browsers only repeat keydown events, not keypress events, + // so we use the suppressKeyPress flag to determine if we've already + // handled the keydown event. #7269 + // Unfortunately the code for & in keypress is the same as the up arrow, + // so we use the suppressKeyPressRepeat flag to avoid handling keypress + // events when we know the keydown event was used to modify the + // search term. #7799 + var suppressKeyPress, suppressKeyPressRepeat, suppressInput, + nodeName = this.element[0].nodeName.toLowerCase(), + isTextarea = nodeName === "textarea", + isInput = nodeName === "input"; + + this.isMultiLine = + // Textareas are always multi-line + isTextarea ? true : + // Inputs are always single-line, even if inside a contentEditable element + // IE also treats inputs as contentEditable + isInput ? false : + // All other element types are determined by whether or not they're contentEditable + this.element.prop( "isContentEditable" ); + + this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; + this.isNewMenu = true; + + this.element + .addClass( "ui-autocomplete-input" ) + .attr( "autocomplete", "off" ); + + this._on( this.element, { + keydown: function( event ) { + /*jshint maxcomplexity:15*/ + if ( this.element.prop( "readOnly" ) ) { + suppressKeyPress = true; + suppressInput = true; + suppressKeyPressRepeat = true; + return; + } + + suppressKeyPress = false; + suppressInput = false; + suppressKeyPressRepeat = false; + var keyCode = $.ui.keyCode; + switch( event.keyCode ) { + case keyCode.PAGE_UP: + suppressKeyPress = true; + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + suppressKeyPress = true; + this._move( "nextPage", event ); + break; + case keyCode.UP: + suppressKeyPress = true; + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + suppressKeyPress = true; + this._keyEvent( "next", event ); + break; + case keyCode.ENTER: + case keyCode.NUMPAD_ENTER: + // when menu is open and has focus + if ( this.menu.active ) { + // #6055 - Opera still allows the keypress to occur + // which causes forms to submit + suppressKeyPress = true; + event.preventDefault(); + this.menu.select( event ); + } + break; + case keyCode.TAB: + if ( this.menu.active ) { + this.menu.select( event ); + } + break; + case keyCode.ESCAPE: + if ( this.menu.element.is( ":visible" ) ) { + this._value( this.term ); + this.close( event ); + // Different browsers have different default behavior for escape + // Single press can mean undo or clear + // Double press in IE means clear the whole form + event.preventDefault(); + } + break; + default: + suppressKeyPressRepeat = true; + // search timeout should be triggered before the input value is changed + this._searchTimeout( event ); + break; + } + }, + keypress: function( event ) { + if ( suppressKeyPress ) { + suppressKeyPress = false; + event.preventDefault(); + return; + } + if ( suppressKeyPressRepeat ) { + return; + } + + // replicate some key handlers to allow them to repeat in Firefox and Opera + var keyCode = $.ui.keyCode; + switch( event.keyCode ) { + case keyCode.PAGE_UP: + this._move( "previousPage", event ); + break; + case keyCode.PAGE_DOWN: + this._move( "nextPage", event ); + break; + case keyCode.UP: + this._keyEvent( "previous", event ); + break; + case keyCode.DOWN: + this._keyEvent( "next", event ); + break; + } + }, + input: function( event ) { + if ( suppressInput ) { + suppressInput = false; + event.preventDefault(); + return; + } + this._searchTimeout( event ); + }, + focus: function() { + this.selectedItem = null; + this.previous = this._value(); + }, + blur: function( event ) { + if ( this.cancelBlur ) { + delete this.cancelBlur; + return; + } + + clearTimeout( this.searching ); + this.close( event ); + this._change( event ); + } + }); + + this._initSource(); + this.menu = $( "<ul>" ) + .addClass( "ui-autocomplete ui-front" ) + .appendTo( this._appendTo() ) + .menu({ + // custom key handling for now + input: $(), + // disable ARIA support, the live region takes care of that + role: null + }) + .hide() + .data( "ui-menu" ); + + this._on( this.menu.element, { + mousedown: function( event ) { + // prevent moving focus out of the text field + event.preventDefault(); + + // IE doesn't prevent moving focus even with event.preventDefault() + // so we set a flag to know when we should ignore the blur event + this.cancelBlur = true; + this._delay(function() { + delete this.cancelBlur; + }); + + // clicking on the scrollbar causes focus to shift to the body + // but we can't detect a mouseup or a click immediately afterward + // so we have to track the next mousedown and close the menu if + // the user clicks somewhere outside of the autocomplete + var menuElement = this.menu.element[ 0 ]; + if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { + this._delay(function() { + var that = this; + this.document.one( "mousedown", function( event ) { + if ( event.target !== that.element[ 0 ] && + event.target !== menuElement && + !$.contains( menuElement, event.target ) ) { + that.close(); + } + }); + }); + } + }, + menufocus: function( event, ui ) { + // support: Firefox + // Prevent accidental activation of menu items in Firefox (#7024 #9118) + if ( this.isNewMenu ) { + this.isNewMenu = false; + if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { + this.menu.blur(); + + this.document.one( "mousemove", function() { + $( event.target ).trigger( event.originalEvent ); + }); + + return; + } + } + + var item = ui.item.data( "ui-autocomplete-item" ); + if ( false !== this._trigger( "focus", event, { item: item } ) ) { + // use value to match what will end up in the input, if it was a key event + if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { + this._value( item.value ); + } + } else { + // Normally the input is populated with the item's value as the + // menu is navigated, causing screen readers to notice a change and + // announce the item. Since the focus event was canceled, this doesn't + // happen, so we update the live region so that screen readers can + // still notice the change and announce it. + this.liveRegion.text( item.value ); + } + }, + menuselect: function( event, ui ) { + var item = ui.item.data( "ui-autocomplete-item" ), + previous = this.previous; + + // only trigger when focus was lost (click on menu) + if ( this.element[0] !== this.document[0].activeElement ) { + this.element.focus(); + this.previous = previous; + // #6109 - IE triggers two focus events and the second + // is asynchronous, so we need to reset the previous + // term synchronously and asynchronously :-( + this._delay(function() { + this.previous = previous; + this.selectedItem = item; + }); + } + + if ( false !== this._trigger( "select", event, { item: item } ) ) { + this._value( item.value ); + } + // reset the term after the select event + // this allows custom select handling to work properly + this.term = this._value(); + + this.close( event ); + this.selectedItem = item; + } + }); + + this.liveRegion = $( "<span>", { + role: "status", + "aria-live": "polite" + }) + .addClass( "ui-helper-hidden-accessible" ) + .insertAfter( this.element ); + + // turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._on( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + }); + }, + + _destroy: function() { + clearTimeout( this.searching ); + this.element + .removeClass( "ui-autocomplete-input" ) + .removeAttr( "autocomplete" ); + this.menu.element.remove(); + this.liveRegion.remove(); + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "source" ) { + this._initSource(); + } + if ( key === "appendTo" ) { + this.menu.element.appendTo( this._appendTo() ); + } + if ( key === "disabled" && value && this.xhr ) { + this.xhr.abort(); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + + if ( element ) { + element = element.jquery || element.nodeType ? + $( element ) : + this.document.find( element ).eq( 0 ); + } + + if ( !element ) { + element = this.element.closest( ".ui-front" ); + } + + if ( !element.length ) { + element = this.document[0].body; + } + + return element; + }, + + _initSource: function() { + var array, url, + that = this; + if ( $.isArray(this.options.source) ) { + array = this.options.source; + this.source = function( request, response ) { + response( $.ui.autocomplete.filter( array, request.term ) ); + }; + } else if ( typeof this.options.source === "string" ) { + url = this.options.source; + this.source = function( request, response ) { + if ( that.xhr ) { + that.xhr.abort(); + } + that.xhr = $.ajax({ + url: url, + data: request, + dataType: "json", + success: function( data ) { + response( data ); + }, + error: function() { + response( [] ); + } + }); + }; + } else { + this.source = this.options.source; + } + }, + + _searchTimeout: function( event ) { + clearTimeout( this.searching ); + this.searching = this._delay(function() { + // only search if the value has changed + if ( this.term !== this._value() ) { + this.selectedItem = null; + this.search( null, event ); + } + }, this.options.delay ); + }, + + search: function( value, event ) { + value = value != null ? value : this._value(); + + // always save the actual value, not the one passed as an argument + this.term = this._value(); + + if ( value.length < this.options.minLength ) { + return this.close( event ); + } + + if ( this._trigger( "search", event ) === false ) { + return; + } + + return this._search( value ); + }, + + _search: function( value ) { + this.pending++; + this.element.addClass( "ui-autocomplete-loading" ); + this.cancelSearch = false; + + this.source( { term: value }, this._response() ); + }, + + _response: function() { + var that = this, + index = ++requestIndex; + + return function( content ) { + if ( index === requestIndex ) { + that.__response( content ); + } + + that.pending--; + if ( !that.pending ) { + that.element.removeClass( "ui-autocomplete-loading" ); + } + }; + }, + + __response: function( content ) { + if ( content ) { + content = this._normalize( content ); + } + this._trigger( "response", null, { content: content } ); + if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { + this._suggest( content ); + this._trigger( "open" ); + } else { + // use ._close() instead of .close() so we don't cancel future searches + this._close(); + } + }, + + close: function( event ) { + this.cancelSearch = true; + this._close( event ); + }, + + _close: function( event ) { + if ( this.menu.element.is( ":visible" ) ) { + this.menu.element.hide(); + this.menu.blur(); + this.isNewMenu = true; + this._trigger( "close", event ); + } + }, + + _change: function( event ) { + if ( this.previous !== this._value() ) { + this._trigger( "change", event, { item: this.selectedItem } ); + } + }, + + _normalize: function( items ) { + // assume all items have the right format when the first item is complete + if ( items.length && items[0].label && items[0].value ) { + return items; + } + return $.map( items, function( item ) { + if ( typeof item === "string" ) { + return { + label: item, + value: item + }; + } + return $.extend({ + label: item.label || item.value, + value: item.value || item.label + }, item ); + }); + }, + + _suggest: function( items ) { + var ul = this.menu.element.empty(); + this._renderMenu( ul, items ); + this.isNewMenu = true; + this.menu.refresh(); + + // size and position menu + ul.show(); + this._resizeMenu(); + ul.position( $.extend({ + of: this.element + }, this.options.position )); + + if ( this.options.autoFocus ) { + this.menu.next(); + } + }, + + _resizeMenu: function() { + var ul = this.menu.element; + ul.outerWidth( Math.max( + // Firefox wraps long text (possibly a rounding bug) + // so we add 1px to avoid the wrapping (#7513) + ul.width( "" ).outerWidth() + 1, + this.element.outerWidth() + ) ); + }, + + _renderMenu: function( ul, items ) { + var that = this; + $.each( items, function( index, item ) { + that._renderItemData( ul, item ); + }); + }, + + _renderItemData: function( ul, item ) { + return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); + }, + + _renderItem: function( ul, item ) { + return $( "<li>" ) + .append( $( "<a>" ).text( item.label ) ) + .appendTo( ul ); + }, + + _move: function( direction, event ) { + if ( !this.menu.element.is( ":visible" ) ) { + this.search( null, event ); + return; + } + if ( this.menu.isFirstItem() && /^previous/.test( direction ) || + this.menu.isLastItem() && /^next/.test( direction ) ) { + this._value( this.term ); + this.menu.blur(); + return; + } + this.menu[ direction ]( event ); + }, + + widget: function() { + return this.menu.element; + }, + + _value: function() { + return this.valueMethod.apply( this.element, arguments ); + }, + + _keyEvent: function( keyEvent, event ) { + if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { + this._move( keyEvent, event ); + + // prevents moving cursor to beginning/end of the text field in some browsers + event.preventDefault(); + } + } +}); + +$.extend( $.ui.autocomplete, { + escapeRegex: function( value ) { + return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); + }, + filter: function(array, term) { + var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); + return $.grep( array, function(value) { + return matcher.test( value.label || value.value || value ); + }); + } +}); + + +// live region extension, adding a `messages` option +// NOTE: This is an experimental API. We are still investigating +// a full solution for string manipulation and internationalization. +$.widget( "ui.autocomplete", $.ui.autocomplete, { + options: { + messages: { + noResults: "No search results.", + results: function( amount ) { + return amount + ( amount > 1 ? " results are" : " result is" ) + + " available, use up and down arrow keys to navigate."; + } + } + }, + + __response: function( content ) { + var message; + this._superApply( arguments ); + if ( this.options.disabled || this.cancelSearch ) { + return; + } + if ( content && content.length ) { + message = this.options.messages.results( content.length ); + } else { + message = this.options.messages.noResults; + } + this.liveRegion.text( message ); + } +}); + +}( jQuery )); + +(function( $, undefined ) { + +var lastActive, startXPos, startYPos, clickDragged, + baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", + stateClasses = "ui-state-hover ui-state-active ", + typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", + formResetHandler = function() { + var buttons = $( this ).find( ":ui-button" ); + setTimeout(function() { + buttons.button( "refresh" ); + }, 1 ); + }, + radioGroup = function( radio ) { + var name = radio.name, + form = radio.form, + radios = $( [] ); + if ( name ) { + name = name.replace( /'/g, "\\'" ); + if ( form ) { + radios = $( form ).find( "[name='" + name + "']" ); + } else { + radios = $( "[name='" + name + "']", radio.ownerDocument ) + .filter(function() { + return !this.form; + }); + } + } + return radios; + }; + +$.widget( "ui.button", { + version: "1.10.2", + defaultElement: "<button>", + options: { + disabled: null, + text: true, + label: null, + icons: { + primary: null, + secondary: null + } + }, + _create: function() { + this.element.closest( "form" ) + .unbind( "reset" + this.eventNamespace ) + .bind( "reset" + this.eventNamespace, formResetHandler ); + + if ( typeof this.options.disabled !== "boolean" ) { + this.options.disabled = !!this.element.prop( "disabled" ); + } else { + this.element.prop( "disabled", this.options.disabled ); + } + + this._determineButtonType(); + this.hasTitle = !!this.buttonElement.attr( "title" ); + + var that = this, + options = this.options, + toggleButton = this.type === "checkbox" || this.type === "radio", + activeClass = !toggleButton ? "ui-state-active" : "", + focusClass = "ui-state-focus"; + + if ( options.label === null ) { + options.label = (this.type === "input" ? this.buttonElement.val() : this.buttonElement.html()); + } + + this._hoverable( this.buttonElement ); + + this.buttonElement + .addClass( baseClasses ) + .attr( "role", "button" ) + .bind( "mouseenter" + this.eventNamespace, function() { + if ( options.disabled ) { + return; + } + if ( this === lastActive ) { + $( this ).addClass( "ui-state-active" ); + } + }) + .bind( "mouseleave" + this.eventNamespace, function() { + if ( options.disabled ) { + return; + } + $( this ).removeClass( activeClass ); + }) + .bind( "click" + this.eventNamespace, function( event ) { + if ( options.disabled ) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + }); + + this.element + .bind( "focus" + this.eventNamespace, function() { + // no need to check disabled, focus won't be triggered anyway + that.buttonElement.addClass( focusClass ); + }) + .bind( "blur" + this.eventNamespace, function() { + that.buttonElement.removeClass( focusClass ); + }); + + if ( toggleButton ) { + this.element.bind( "change" + this.eventNamespace, function() { + if ( clickDragged ) { + return; + } + that.refresh(); + }); + // if mouse moves between mousedown and mouseup (drag) set clickDragged flag + // prevents issue where button state changes but checkbox/radio checked state + // does not in Firefox (see ticket #6970) + this.buttonElement + .bind( "mousedown" + this.eventNamespace, function( event ) { + if ( options.disabled ) { + return; + } + clickDragged = false; + startXPos = event.pageX; + startYPos = event.pageY; + }) + .bind( "mouseup" + this.eventNamespace, function( event ) { + if ( options.disabled ) { + return; + } + if ( startXPos !== event.pageX || startYPos !== event.pageY ) { + clickDragged = true; + } + }); + } + + if ( this.type === "checkbox" ) { + this.buttonElement.bind( "click" + this.eventNamespace, function() { + if ( options.disabled || clickDragged ) { + return false; + } + }); + } else if ( this.type === "radio" ) { + this.buttonElement.bind( "click" + this.eventNamespace, function() { + if ( options.disabled || clickDragged ) { + return false; + } + $( this ).addClass( "ui-state-active" ); + that.buttonElement.attr( "aria-pressed", "true" ); + + var radio = that.element[ 0 ]; + radioGroup( radio ) + .not( radio ) + .map(function() { + return $( this ).button( "widget" )[ 0 ]; + }) + .removeClass( "ui-state-active" ) + .attr( "aria-pressed", "false" ); + }); + } else { + this.buttonElement + .bind( "mousedown" + this.eventNamespace, function() { + if ( options.disabled ) { + return false; + } + $( this ).addClass( "ui-state-active" ); + lastActive = this; + that.document.one( "mouseup", function() { + lastActive = null; + }); + }) + .bind( "mouseup" + this.eventNamespace, function() { + if ( options.disabled ) { + return false; + } + $( this ).removeClass( "ui-state-active" ); + }) + .bind( "keydown" + this.eventNamespace, function(event) { + if ( options.disabled ) { + return false; + } + if ( event.keyCode === $.ui.keyCode.SPACE || event.keyCode === $.ui.keyCode.ENTER ) { + $( this ).addClass( "ui-state-active" ); + } + }) + // see #8559, we bind to blur here in case the button element loses + // focus between keydown and keyup, it would be left in an "active" state + .bind( "keyup" + this.eventNamespace + " blur" + this.eventNamespace, function() { + $( this ).removeClass( "ui-state-active" ); + }); + + if ( this.buttonElement.is("a") ) { + this.buttonElement.keyup(function(event) { + if ( event.keyCode === $.ui.keyCode.SPACE ) { + // TODO pass through original event correctly (just as 2nd argument doesn't work) + $( this ).click(); + } + }); + } + } + + // TODO: pull out $.Widget's handling for the disabled option into + // $.Widget.prototype._setOptionDisabled so it's easy to proxy and can + // be overridden by individual plugins + this._setOption( "disabled", options.disabled ); + this._resetButton(); + }, + + _determineButtonType: function() { + var ancestor, labelSelector, checked; + + if ( this.element.is("[type=checkbox]") ) { + this.type = "checkbox"; + } else if ( this.element.is("[type=radio]") ) { + this.type = "radio"; + } else if ( this.element.is("input") ) { + this.type = "input"; + } else { + this.type = "button"; + } + + if ( this.type === "checkbox" || this.type === "radio" ) { + // we don't search against the document in case the element + // is disconnected from the DOM + ancestor = this.element.parents().last(); + labelSelector = "label[for='" + this.element.attr("id") + "']"; + this.buttonElement = ancestor.find( labelSelector ); + if ( !this.buttonElement.length ) { + ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings(); + this.buttonElement = ancestor.filter( labelSelector ); + if ( !this.buttonElement.length ) { + this.buttonElement = ancestor.find( labelSelector ); + } + } + this.element.addClass( "ui-helper-hidden-accessible" ); + + checked = this.element.is( ":checked" ); + if ( checked ) { + this.buttonElement.addClass( "ui-state-active" ); + } + this.buttonElement.prop( "aria-pressed", checked ); + } else { + this.buttonElement = this.element; + } + }, + + widget: function() { + return this.buttonElement; + }, + + _destroy: function() { + this.element + .removeClass( "ui-helper-hidden-accessible" ); + this.buttonElement + .removeClass( baseClasses + " " + stateClasses + " " + typeClasses ) + .removeAttr( "role" ) + .removeAttr( "aria-pressed" ) + .html( this.buttonElement.find(".ui-button-text").html() ); + + if ( !this.hasTitle ) { + this.buttonElement.removeAttr( "title" ); + } + }, + + _setOption: function( key, value ) { + this._super( key, value ); + if ( key === "disabled" ) { + if ( value ) { + this.element.prop( "disabled", true ); + } else { + this.element.prop( "disabled", false ); + } + return; + } + this._resetButton(); + }, + + refresh: function() { + //See #8237 & #8828 + var isDisabled = this.element.is( "input, button" ) ? this.element.is( ":disabled" ) : this.element.hasClass( "ui-button-disabled" ); + + if ( isDisabled !== this.options.disabled ) { + this._setOption( "disabled", isDisabled ); + } + if ( this.type === "radio" ) { + radioGroup( this.element[0] ).each(function() { + if ( $( this ).is( ":checked" ) ) { + $( this ).button( "widget" ) + .addClass( "ui-state-active" ) + .attr( "aria-pressed", "true" ); + } else { + $( this ).button( "widget" ) + .removeClass( "ui-state-active" ) + .attr( "aria-pressed", "false" ); + } + }); + } else if ( this.type === "checkbox" ) { + if ( this.element.is( ":checked" ) ) { + this.buttonElement + .addClass( "ui-state-active" ) + .attr( "aria-pressed", "true" ); + } else { + this.buttonElement + .removeClass( "ui-state-active" ) + .attr( "aria-pressed", "false" ); + } + } + }, + + _resetButton: function() { + if ( this.type === "input" ) { + if ( this.options.label ) { + this.element.val( this.options.label ); + } + return; + } + var buttonElement = this.buttonElement.removeClass( typeClasses ), + buttonText = $( "<span></span>", this.document[0] ) + .addClass( "ui-button-text" ) + .html( this.options.label ) + .appendTo( buttonElement.empty() ) + .text(), + icons = this.options.icons, + multipleIcons = icons.primary && icons.secondary, + buttonClasses = []; + + if ( icons.primary || icons.secondary ) { + if ( this.options.text ) { + buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) ); + } + + if ( icons.primary ) { + buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" ); + } + + if ( icons.secondary ) { + buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" ); + } + + if ( !this.options.text ) { + buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" ); + + if ( !this.hasTitle ) { + buttonElement.attr( "title", $.trim( buttonText ) ); + } + } + } else { + buttonClasses.push( "ui-button-text-only" ); + } + buttonElement.addClass( buttonClasses.join( " " ) ); + } +}); + +$.widget( "ui.buttonset", { + version: "1.10.2", + options: { + items: "button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(ui-button)" + }, + + _create: function() { + this.element.addClass( "ui-buttonset" ); + }, + + _init: function() { + this.refresh(); + }, + + _setOption: function( key, value ) { + if ( key === "disabled" ) { + this.buttons.button( "option", key, value ); + } + + this._super( key, value ); + }, + + refresh: function() { + var rtl = this.element.css( "direction" ) === "rtl"; + + this.buttons = this.element.find( this.options.items ) + .filter( ":ui-button" ) + .button( "refresh" ) + .end() + .not( ":ui-button" ) + .button() + .end() + .map(function() { + return $( this ).button( "widget" )[ 0 ]; + }) + .removeClass( "ui-corner-all ui-corner-left ui-corner-right" ) + .filter( ":first" ) + .addClass( rtl ? "ui-corner-right" : "ui-corner-left" ) + .end() + .filter( ":last" ) + .addClass( rtl ? "ui-corner-left" : "ui-corner-right" ) + .end() + .end(); + }, + + _destroy: function() { + this.element.removeClass( "ui-buttonset" ); + this.buttons + .map(function() { + return $( this ).button( "widget" )[ 0 ]; + }) + .removeClass( "ui-corner-left ui-corner-right" ) + .end() + .button( "destroy" ); + } +}); + +}( jQuery ) ); + +(function( $, undefined ) { + +$.extend($.ui, { datepicker: { version: "1.10.2" } }); + +var PROP_NAME = "datepicker", + dpuuid = new Date().getTime(), + instActive; + +/* Date picker manager. + Use the singleton instance of this class, $.datepicker, to interact with the date picker. + Settings for (groups of) date pickers are maintained in an instance object, + allowing multiple different settings on the same page. */ + +function Datepicker() { + this._curInst = null; // The current instance in use + this._keyEvent = false; // If the last event was a key event + this._disabledInputs = []; // List of date picker inputs that have been disabled + this._datepickerShowing = false; // True if the popup picker is showing , false if not + this._inDialog = false; // True if showing within a "dialog", false if not + this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division + this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class + this._appendClass = "ui-datepicker-append"; // The name of the append marker class + this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class + this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class + this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class + this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class + this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class + this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class + this.regional = []; // Available regional settings, indexed by language code + this.regional[""] = { // Default regional settings + closeText: "Done", // Display text for close link + prevText: "Prev", // Display text for previous month link + nextText: "Next", // Display text for next month link + currentText: "Today", // Display text for current month link + monthNames: ["January","February","March","April","May","June", + "July","August","September","October","November","December"], // Names of months for drop-down and formatting + monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], // For formatting + dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], // For formatting + dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], // For formatting + dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"], // Column headings for days starting at Sunday + weekHeader: "Wk", // Column header for week of the year + dateFormat: "mm/dd/yy", // See format options on parseDate + firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... + isRTL: false, // True if right-to-left language, false if left-to-right + showMonthAfterYear: false, // True if the year select precedes month, false for month then year + yearSuffix: "" // Additional text to append to the year in the month headers + }; + this._defaults = { // Global defaults for all the date picker instances + showOn: "focus", // "focus" for popup on focus, + // "button" for trigger button, or "both" for either + showAnim: "fadeIn", // Name of jQuery animation for popup + showOptions: {}, // Options for enhanced animations + defaultDate: null, // Used when field is blank: actual date, + // +/-number for offset from today, null for today + appendText: "", // Display text following the input box, e.g. showing the format + buttonText: "...", // Text for trigger button + buttonImage: "", // URL for trigger button image + buttonImageOnly: false, // True if the image appears alone, false if it appears on a button + hideIfNoPrevNext: false, // True to hide next/previous month links + // if not applicable, false to just disable them + navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links + gotoCurrent: false, // True if today link goes back to current selection instead + changeMonth: false, // True if month can be selected directly, false if only prev/next + changeYear: false, // True if year can be selected directly, false if only prev/next + yearRange: "c-10:c+10", // Range of years to display in drop-down, + // either relative to today's year (-nn:+nn), relative to currently displayed year + // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n) + showOtherMonths: false, // True to show dates in other months, false to leave blank + selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable + showWeek: false, // True to show week of the year, false to not show it + calculateWeek: this.iso8601Week, // How to calculate the week of the year, + // takes a Date and returns the number of the week for it + shortYearCutoff: "+10", // Short year values < this are in the current century, + // > this are in the previous century, + // string value starting with "+" for current year + value + minDate: null, // The earliest selectable date, or null for no limit + maxDate: null, // The latest selectable date, or null for no limit + duration: "fast", // Duration of display/closure + beforeShowDay: null, // Function that takes a date and returns an array with + // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "", + // [2] = cell title (optional), e.g. $.datepicker.noWeekends + beforeShow: null, // Function that takes an input field and + // returns a set of custom settings for the date picker + onSelect: null, // Define a callback function when a date is selected + onChangeMonthYear: null, // Define a callback function when the month or year is changed + onClose: null, // Define a callback function when the datepicker is closed + numberOfMonths: 1, // Number of months to show at a time + showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) + stepMonths: 1, // Number of months to step back/forward + stepBigMonths: 12, // Number of months to step back/forward for the big links + altField: "", // Selector for an alternate field to store selected dates into + altFormat: "", // The date format to use for the alternate field + constrainInput: true, // The input is constrained by the current date format + showButtonPanel: false, // True to show button panel, false to not show it + autoSize: false, // True to size the input for the date format, false to leave as is + disabled: false // The initial disabled state + }; + $.extend(this._defaults, this.regional[""]); + this.dpDiv = bindHover($("<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")); +} + +$.extend(Datepicker.prototype, { + /* Class name added to elements to indicate already configured with a date picker. */ + markerClassName: "hasDatepicker", + + //Keep track of the maximum number of rows displayed (see #7043) + maxRows: 4, + + // TODO rename to "widget" when switching to widget factory + _widgetDatepicker: function() { + return this.dpDiv; + }, + + /* Override the default settings for all instances of the date picker. + * @param settings object - the new settings to use as defaults (anonymous object) + * @return the manager object + */ + setDefaults: function(settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + /* Attach the date picker to a jQuery selection. + * @param target element - the target input field or division or span + * @param settings object - the new settings to use for this date picker instance (anonymous) + */ + _attachDatepicker: function(target, settings) { + var nodeName, inline, inst; + nodeName = target.nodeName.toLowerCase(); + inline = (nodeName === "div" || nodeName === "span"); + if (!target.id) { + this.uuid += 1; + target.id = "dp" + this.uuid; + } + inst = this._newInst($(target), inline); + inst.settings = $.extend({}, settings || {}); + if (nodeName === "input") { + this._connectDatepicker(target, inst); + } else if (inline) { + this._inlineDatepicker(target, inst); + } + }, + + /* Create a new instance object. */ + _newInst: function(target, inline) { + var id = target[0].id.replace(/([^A-Za-z0-9_\-])/g, "\\\\$1"); // escape jQuery meta chars + return {id: id, input: target, // associated target + selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection + drawMonth: 0, drawYear: 0, // month being drawn + inline: inline, // is datepicker inline or not + dpDiv: (!inline ? this.dpDiv : // presentation div + bindHover($("<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")))}; + }, + + /* Attach the date picker to an input field. */ + _connectDatepicker: function(target, inst) { + var input = $(target); + inst.append = $([]); + inst.trigger = $([]); + if (input.hasClass(this.markerClassName)) { + return; + } + this._attachments(input, inst); + input.addClass(this.markerClassName).keydown(this._doKeyDown). + keypress(this._doKeyPress).keyup(this._doKeyUp); + this._autoSize(inst); + $.data(target, PROP_NAME, inst); + //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665) + if( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + }, + + /* Make attachments based on settings. */ + _attachments: function(input, inst) { + var showOn, buttonText, buttonImage, + appendText = this._get(inst, "appendText"), + isRTL = this._get(inst, "isRTL"); + + if (inst.append) { + inst.append.remove(); + } + if (appendText) { + inst.append = $("<span class='" + this._appendClass + "'>" + appendText + "</span>"); + input[isRTL ? "before" : "after"](inst.append); + } + + input.unbind("focus", this._showDatepicker); + + if (inst.trigger) { + inst.trigger.remove(); + } + + showOn = this._get(inst, "showOn"); + if (showOn === "focus" || showOn === "both") { // pop-up date picker when in the marked field + input.focus(this._showDatepicker); + } + if (showOn === "button" || showOn === "both") { // pop-up date picker when button clicked + buttonText = this._get(inst, "buttonText"); + buttonImage = this._get(inst, "buttonImage"); + inst.trigger = $(this._get(inst, "buttonImageOnly") ? + $("<img/>").addClass(this._triggerClass). + attr({ src: buttonImage, alt: buttonText, title: buttonText }) : + $("<button type='button'></button>").addClass(this._triggerClass). + html(!buttonImage ? buttonText : $("<img/>").attr( + { src:buttonImage, alt:buttonText, title:buttonText }))); + input[isRTL ? "before" : "after"](inst.trigger); + inst.trigger.click(function() { + if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { + $.datepicker._hideDatepicker(); + } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { + $.datepicker._hideDatepicker(); + $.datepicker._showDatepicker(input[0]); + } else { + $.datepicker._showDatepicker(input[0]); + } + return false; + }); + } + }, + + /* Apply the maximum length for the date format. */ + _autoSize: function(inst) { + if (this._get(inst, "autoSize") && !inst.inline) { + var findMax, max, maxI, i, + date = new Date(2009, 12 - 1, 20), // Ensure double digits + dateFormat = this._get(inst, "dateFormat"); + + if (dateFormat.match(/[DM]/)) { + findMax = function(names) { + max = 0; + maxI = 0; + for (i = 0; i < names.length; i++) { + if (names[i].length > max) { + max = names[i].length; + maxI = i; + } + } + return maxI; + }; + date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? + "monthNames" : "monthNamesShort")))); + date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? + "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); + } + inst.input.attr("size", this._formatDate(inst, date).length); + } + }, + + /* Attach an inline date picker to a div. */ + _inlineDatepicker: function(target, inst) { + var divSpan = $(target); + if (divSpan.hasClass(this.markerClassName)) { + return; + } + divSpan.addClass(this.markerClassName).append(inst.dpDiv); + $.data(target, PROP_NAME, inst); + this._setDate(inst, this._getDefaultDate(inst), true); + this._updateDatepicker(inst); + this._updateAlternate(inst); + //If disabled option is true, disable the datepicker before showing it (see ticket #5665) + if( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements + // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height + inst.dpDiv.css( "display", "block" ); + }, + + /* Pop-up the date picker in a "dialog" box. + * @param input element - ignored + * @param date string or Date - the initial date to display + * @param onSelect function - the function to call when a date is selected + * @param settings object - update the dialog date picker instance's settings (anonymous object) + * @param pos int[2] - coordinates for the dialog's position within the screen or + * event - with x/y coordinates or + * leave empty for default (screen centre) + * @return the manager object + */ + _dialogDatepicker: function(input, date, onSelect, settings, pos) { + var id, browserWidth, browserHeight, scrollX, scrollY, + inst = this._dialogInst; // internal instance + + if (!inst) { + this.uuid += 1; + id = "dp" + this.uuid; + this._dialogInput = $("<input type='text' id='" + id + + "' style='position: absolute; top: -100px; width: 0px;'/>"); + this._dialogInput.keydown(this._doKeyDown); + $("body").append(this._dialogInput); + inst = this._dialogInst = this._newInst(this._dialogInput, false); + inst.settings = {}; + $.data(this._dialogInput[0], PROP_NAME, inst); + } + extendRemove(inst.settings, settings || {}); + date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); + this._dialogInput.val(date); + + this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); + if (!this._pos) { + browserWidth = document.documentElement.clientWidth; + browserHeight = document.documentElement.clientHeight; + scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; + scrollY = document.documentElement.scrollTop || document.body.scrollTop; + this._pos = // should use actual width/height below + [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; + } + + // move input on screen for focus, but hidden behind dialog + this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); + inst.settings.onSelect = onSelect; + this._inDialog = true; + this.dpDiv.addClass(this._dialogClass); + this._showDatepicker(this._dialogInput[0]); + if ($.blockUI) { + $.blockUI(this.dpDiv); + } + $.data(this._dialogInput[0], PROP_NAME, inst); + return this; + }, + + /* Detach a datepicker from its control. + * @param target element - the target input field or division or span + */ + _destroyDatepicker: function(target) { + var nodeName, + $target = $(target), + inst = $.data(target, PROP_NAME); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + $.removeData(target, PROP_NAME); + if (nodeName === "input") { + inst.append.remove(); + inst.trigger.remove(); + $target.removeClass(this.markerClassName). + unbind("focus", this._showDatepicker). + unbind("keydown", this._doKeyDown). + unbind("keypress", this._doKeyPress). + unbind("keyup", this._doKeyUp); + } else if (nodeName === "div" || nodeName === "span") { + $target.removeClass(this.markerClassName).empty(); + } + }, + + /* Enable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _enableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, PROP_NAME); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = false; + inst.trigger.filter("button"). + each(function() { this.disabled = false; }).end(). + filter("img").css({opacity: "1.0", cursor: ""}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().removeClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", false); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + }, + + /* Disable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _disableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, PROP_NAME); + + if (!$target.hasClass(this.markerClassName)) { + return; + } + + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = true; + inst.trigger.filter("button"). + each(function() { this.disabled = true; }).end(). + filter("img").css({opacity: "0.5", cursor: "default"}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().addClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", true); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + this._disabledInputs[this._disabledInputs.length] = target; + }, + + /* Is the first field in a jQuery collection disabled as a datepicker? + * @param target element - the target input field or division or span + * @return boolean - true if disabled, false if enabled + */ + _isDisabledDatepicker: function(target) { + if (!target) { + return false; + } + for (var i = 0; i < this._disabledInputs.length; i++) { + if (this._disabledInputs[i] === target) { + return true; + } + } + return false; + }, + + /* Retrieve the instance data for the target control. + * @param target element - the target input field or division or span + * @return object - the associated instance data + * @throws error if a jQuery problem getting data + */ + _getInst: function(target) { + try { + return $.data(target, PROP_NAME); + } + catch (err) { + throw "Missing instance data for this datepicker"; + } + }, + + /* Update or retrieve the settings for a date picker attached to an input field or division. + * @param target element - the target input field or division or span + * @param name object - the new settings to update or + * string - the name of the setting to change or retrieve, + * when retrieving also "all" for all instance settings or + * "defaults" for all global defaults + * @param value any - the new value for the setting + * (omit if above is an object or to retrieve a value) + */ + _optionDatepicker: function(target, name, value) { + var settings, date, minDate, maxDate, + inst = this._getInst(target); + + if (arguments.length === 2 && typeof name === "string") { + return (name === "defaults" ? $.extend({}, $.datepicker._defaults) : + (inst ? (name === "all" ? $.extend({}, inst.settings) : + this._get(inst, name)) : null)); + } + + settings = name || {}; + if (typeof name === "string") { + settings = {}; + settings[name] = value; + } + + if (inst) { + if (this._curInst === inst) { + this._hideDatepicker(); + } + + date = this._getDateDatepicker(target, true); + minDate = this._getMinMaxDate(inst, "min"); + maxDate = this._getMinMaxDate(inst, "max"); + extendRemove(inst.settings, settings); + // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided + if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) { + inst.settings.minDate = this._formatDate(inst, minDate); + } + if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) { + inst.settings.maxDate = this._formatDate(inst, maxDate); + } + if ( "disabled" in settings ) { + if ( settings.disabled ) { + this._disableDatepicker(target); + } else { + this._enableDatepicker(target); + } + } + this._attachments($(target), inst); + this._autoSize(inst); + this._setDate(inst, date); + this._updateAlternate(inst); + this._updateDatepicker(inst); + } + }, + + // change method deprecated + _changeDatepicker: function(target, name, value) { + this._optionDatepicker(target, name, value); + }, + + /* Redraw the date picker attached to an input field or division. + * @param target element - the target input field or division or span + */ + _refreshDatepicker: function(target) { + var inst = this._getInst(target); + if (inst) { + this._updateDatepicker(inst); + } + }, + + /* Set the dates for a jQuery selection. + * @param target element - the target input field or division or span + * @param date Date - the new date + */ + _setDateDatepicker: function(target, date) { + var inst = this._getInst(target); + if (inst) { + this._setDate(inst, date); + this._updateDatepicker(inst); + this._updateAlternate(inst); + } + }, + + /* Get the date(s) for the first entry in a jQuery selection. + * @param target element - the target input field or division or span + * @param noDefault boolean - true if no default date is to be used + * @return Date - the current date + */ + _getDateDatepicker: function(target, noDefault) { + var inst = this._getInst(target); + if (inst && !inst.inline) { + this._setDateFromField(inst, noDefault); + } + return (inst ? this._getDate(inst) : null); + }, + + /* Handle keystrokes. */ + _doKeyDown: function(event) { + var onSelect, dateStr, sel, + inst = $.datepicker._getInst(event.target), + handled = true, + isRTL = inst.dpDiv.is(".ui-datepicker-rtl"); + + inst._keyEvent = true; + if ($.datepicker._datepickerShowing) { + switch (event.keyCode) { + case 9: $.datepicker._hideDatepicker(); + handled = false; + break; // hide on tab out + case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." + + $.datepicker._currentClass + ")", inst.dpDiv); + if (sel[0]) { + $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); + } + + onSelect = $.datepicker._get(inst, "onSelect"); + if (onSelect) { + dateStr = $.datepicker._formatDate(inst); + + // trigger custom callback + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); + } else { + $.datepicker._hideDatepicker(); + } + + return false; // don't submit the form + case 27: $.datepicker._hideDatepicker(); + break; // hide on escape + case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + break; // previous month/year on page up/+ ctrl + case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + break; // next month/year on page down/+ ctrl + case 35: if (event.ctrlKey || event.metaKey) { + $.datepicker._clearDate(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // clear on ctrl or command +end + case 36: if (event.ctrlKey || event.metaKey) { + $.datepicker._gotoToday(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // current on ctrl or command +home + case 37: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // -1 day on ctrl or command +left + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +left on Mac + break; + case 38: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, -7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // -1 week on ctrl or command +up + case 39: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // +1 day on ctrl or command +right + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +right + break; + case 40: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, +7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // +1 week on ctrl or command +down + default: handled = false; + } + } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home + $.datepicker._showDatepicker(this); + } else { + handled = false; + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }, + + /* Filter entered characters - based on date format. */ + _doKeyPress: function(event) { + var chars, chr, + inst = $.datepicker._getInst(event.target); + + if ($.datepicker._get(inst, "constrainInput")) { + chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat")); + chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode); + return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1); + } + }, + + /* Synchronise manual entry and field/alternate field. */ + _doKeyUp: function(event) { + var date, + inst = $.datepicker._getInst(event.target); + + if (inst.input.val() !== inst.lastVal) { + try { + date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + (inst.input ? inst.input.val() : null), + $.datepicker._getFormatConfig(inst)); + + if (date) { // only if valid + $.datepicker._setDateFromField(inst); + $.datepicker._updateAlternate(inst); + $.datepicker._updateDatepicker(inst); + } + } + catch (err) { + } + } + return true; + }, + + /* Pop-up the date picker for a given input field. + * If false returned from beforeShow event handler do not show. + * @param input element - the input field attached to the date picker or + * event - if triggered by focus + */ + _showDatepicker: function(input) { + input = input.target || input; + if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger + input = $("input", input.parentNode)[0]; + } + + if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here + return; + } + + var inst, beforeShow, beforeShowSettings, isFixed, + offset, showAnim, duration; + + inst = $.datepicker._getInst(input); + if ($.datepicker._curInst && $.datepicker._curInst !== inst) { + $.datepicker._curInst.dpDiv.stop(true, true); + if ( inst && $.datepicker._datepickerShowing ) { + $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); + } + } + + beforeShow = $.datepicker._get(inst, "beforeShow"); + beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; + if(beforeShowSettings === false){ + return; + } + extendRemove(inst.settings, beforeShowSettings); + + inst.lastVal = null; + $.datepicker._lastInput = input; + $.datepicker._setDateFromField(inst); + + if ($.datepicker._inDialog) { // hide cursor + input.value = ""; + } + if (!$.datepicker._pos) { // position below input + $.datepicker._pos = $.datepicker._findPos(input); + $.datepicker._pos[1] += input.offsetHeight; // add the height + } + + isFixed = false; + $(input).parents().each(function() { + isFixed |= $(this).css("position") === "fixed"; + return !isFixed; + }); + + offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; + $.datepicker._pos = null; + //to avoid flashes on Firefox + inst.dpDiv.empty(); + // determine sizing offscreen + inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"}); + $.datepicker._updateDatepicker(inst); + // fix width for dynamic number of date pickers + // and adjust position before showing + offset = $.datepicker._checkOffset(inst, offset, isFixed); + inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? + "static" : (isFixed ? "fixed" : "absolute")), display: "none", + left: offset.left + "px", top: offset.top + "px"}); + + if (!inst.inline) { + showAnim = $.datepicker._get(inst, "showAnim"); + duration = $.datepicker._get(inst, "duration"); + inst.dpDiv.zIndex($(input).zIndex()+1); + $.datepicker._datepickerShowing = true; + + if ( $.effects && $.effects.effect[ showAnim ] ) { + inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration); + } else { + inst.dpDiv[showAnim || "show"](showAnim ? duration : null); + } + + if (inst.input.is(":visible") && !inst.input.is(":disabled")) { + inst.input.focus(); + } + $.datepicker._curInst = inst; + } + }, + + /* Generate the date picker content. */ + _updateDatepicker: function(inst) { + this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) + instActive = inst; // for delegate hover events + inst.dpDiv.empty().append(this._generateHTML(inst)); + this._attachHandlers(inst); + inst.dpDiv.find("." + this._dayOverClass + " a").mouseover(); + + var origyearshtml, + numMonths = this._getNumberOfMonths(inst), + cols = numMonths[1], + width = 17; + + inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""); + if (cols > 1) { + inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em"); + } + inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") + + "Class"]("ui-datepicker-multi"); + inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") + + "Class"]("ui-datepicker-rtl"); + + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input && + inst.input.is(":visible") && !inst.input.is(":disabled") && inst.input[0] !== document.activeElement) { + inst.input.focus(); + } + + // deffered render of the years select (to avoid flashes on Firefox) + if( inst.yearshtml ){ + origyearshtml = inst.yearshtml; + setTimeout(function(){ + //assure that inst.yearshtml didn't change. + if( origyearshtml === inst.yearshtml && inst.yearshtml ){ + inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml); + } + origyearshtml = inst.yearshtml = null; + }, 0); + } + }, + + /* Retrieve the size of left and top borders for an element. + * @param elem (jQuery object) the element of interest + * @return (number[2]) the left and top borders + */ + _getBorders: function(elem) { + var convert = function(value) { + return {thin: 1, medium: 2, thick: 3}[value] || value; + }; + return [parseFloat(convert(elem.css("border-left-width"))), + parseFloat(convert(elem.css("border-top-width")))]; + }, + + /* Check positioning to remain on screen. */ + _checkOffset: function(inst, offset, isFixed) { + var dpWidth = inst.dpDiv.outerWidth(), + dpHeight = inst.dpDiv.outerHeight(), + inputWidth = inst.input ? inst.input.outerWidth() : 0, + inputHeight = inst.input ? inst.input.outerHeight() : 0, + viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()), + viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); + + offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0); + offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0; + offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; + + // now check if datepicker is showing outside window viewport - move to a better place if so. + offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? + Math.abs(offset.left + dpWidth - viewWidth) : 0); + offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight) : 0); + + return offset; + }, + + /* Find an object's position on the screen. */ + _findPos: function(obj) { + var position, + inst = this._getInst(obj), + isRTL = this._get(inst, "isRTL"); + + while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) { + obj = obj[isRTL ? "previousSibling" : "nextSibling"]; + } + + position = $(obj).offset(); + return [position.left, position.top]; + }, + + /* Hide the date picker from view. + * @param input element - the input field attached to the date picker + */ + _hideDatepicker: function(input) { + var showAnim, duration, postProcess, onClose, + inst = this._curInst; + + if (!inst || (input && inst !== $.data(input, PROP_NAME))) { + return; + } + + if (this._datepickerShowing) { + showAnim = this._get(inst, "showAnim"); + duration = this._get(inst, "duration"); + postProcess = function() { + $.datepicker._tidyDialog(inst); + }; + + // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed + if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { + inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess); + } else { + inst.dpDiv[(showAnim === "slideDown" ? "slideUp" : + (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess); + } + + if (!showAnim) { + postProcess(); + } + this._datepickerShowing = false; + + onClose = this._get(inst, "onClose"); + if (onClose) { + onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); + } + + this._lastInput = null; + if (this._inDialog) { + this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); + if ($.blockUI) { + $.unblockUI(); + $("body").append(this.dpDiv); + } + } + this._inDialog = false; + } + }, + + /* Tidy up after a dialog display. */ + _tidyDialog: function(inst) { + inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); + }, + + /* Close date picker if clicked elsewhere. */ + _checkExternalClick: function(event) { + if (!$.datepicker._curInst) { + return; + } + + var $target = $(event.target), + inst = $.datepicker._getInst($target[0]); + + if ( ( ( $target[0].id !== $.datepicker._mainDivId && + $target.parents("#" + $.datepicker._mainDivId).length === 0 && + !$target.hasClass($.datepicker.markerClassName) && + !$target.closest("." + $.datepicker._triggerClass).length && + $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || + ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { + $.datepicker._hideDatepicker(); + } + }, + + /* Adjust one of the date sub-fields. */ + _adjustDate: function(id, offset, period) { + var target = $(id), + inst = this._getInst(target[0]); + + if (this._isDisabledDatepicker(target[0])) { + return; + } + this._adjustInstDate(inst, offset + + (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning + period); + this._updateDatepicker(inst); + }, + + /* Action for current link. */ + _gotoToday: function(id) { + var date, + target = $(id), + inst = this._getInst(target[0]); + + if (this._get(inst, "gotoCurrent") && inst.currentDay) { + inst.selectedDay = inst.currentDay; + inst.drawMonth = inst.selectedMonth = inst.currentMonth; + inst.drawYear = inst.selectedYear = inst.currentYear; + } else { + date = new Date(); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + } + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a new month/year. */ + _selectMonthYear: function(id, select, period) { + var target = $(id), + inst = this._getInst(target[0]); + + inst["selected" + (period === "M" ? "Month" : "Year")] = + inst["draw" + (period === "M" ? "Month" : "Year")] = + parseInt(select.options[select.selectedIndex].value,10); + + this._notifyChange(inst); + this._adjustDate(target); + }, + + /* Action for selecting a day. */ + _selectDay: function(id, month, year, td) { + var inst, + target = $(id); + + if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { + return; + } + + inst = this._getInst(target[0]); + inst.selectedDay = inst.currentDay = $("a", td).html(); + inst.selectedMonth = inst.currentMonth = month; + inst.selectedYear = inst.currentYear = year; + this._selectDate(id, this._formatDate(inst, + inst.currentDay, inst.currentMonth, inst.currentYear)); + }, + + /* Erase the input field and hide the date picker. */ + _clearDate: function(id) { + var target = $(id); + this._selectDate(target, ""); + }, + + /* Update the input field with the selected date. */ + _selectDate: function(id, dateStr) { + var onSelect, + target = $(id), + inst = this._getInst(target[0]); + + dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); + if (inst.input) { + inst.input.val(dateStr); + } + this._updateAlternate(inst); + + onSelect = this._get(inst, "onSelect"); + if (onSelect) { + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback + } else if (inst.input) { + inst.input.trigger("change"); // fire the change event + } + + if (inst.inline){ + this._updateDatepicker(inst); + } else { + this._hideDatepicker(); + this._lastInput = inst.input[0]; + if (typeof(inst.input[0]) !== "object") { + inst.input.focus(); // restore focus + } + this._lastInput = null; + } + }, + + /* Update any alternate field to synchronise with the main field. */ + _updateAlternate: function(inst) { + var altFormat, date, dateStr, + altField = this._get(inst, "altField"); + + if (altField) { // update alternate field too + altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); + date = this._getDate(inst); + dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); + $(altField).each(function() { $(this).val(dateStr); }); + } + }, + + /* Set as beforeShowDay function to prevent selection of weekends. + * @param date Date - the date to customise + * @return [boolean, string] - is this date selectable?, what is its CSS class? + */ + noWeekends: function(date) { + var day = date.getDay(); + return [(day > 0 && day < 6), ""]; + }, + + /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + * @param date Date - the date to get the week for + * @return number - the number of the week within the year that contains this date + */ + iso8601Week: function(date) { + var time, + checkDate = new Date(date.getTime()); + + // Find Thursday of this week starting on Monday + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); + + time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + }, + + /* Parse a string value into a date object. + * See formatDate below for the possible formats. + * + * @param format string - the expected format of the date + * @param value string - the date in the above format + * @param settings Object - attributes include: + * shortYearCutoff number - the cutoff year for determining the century (optional) + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return Date - the extracted date value or null if value is blank + */ + parseDate: function (format, value, settings) { + if (format == null || value == null) { + throw "Invalid arguments"; + } + + value = (typeof value === "object" ? value.toString() : value + ""); + if (value === "") { + return null; + } + + var iFormat, dim, extra, + iValue = 0, + shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, + shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : + new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + year = -1, + month = -1, + day = -1, + doy = -1, + literal = false, + date, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Extract a number from the string value + getNumber = function(match) { + var isDoubled = lookAhead(match), + size = (match === "@" ? 14 : (match === "!" ? 20 : + (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), + digits = new RegExp("^\\d{1," + size + "}"), + num = value.substring(iValue).match(digits); + if (!num) { + throw "Missing number at position " + iValue; + } + iValue += num[0].length; + return parseInt(num[0], 10); + }, + // Extract a name from the string value and convert to an index + getName = function(match, shortNames, longNames) { + var index = -1, + names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { + return [ [k, v] ]; + }).sort(function (a, b) { + return -(a[1].length - b[1].length); + }); + + $.each(names, function (i, pair) { + var name = pair[1]; + if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { + index = pair[0]; + iValue += name.length; + return false; + } + }); + if (index !== -1) { + return index + 1; + } else { + throw "Unknown name at position " + iValue; + } + }, + // Confirm that a literal character matches the string value + checkLiteral = function() { + if (value.charAt(iValue) !== format.charAt(iFormat)) { + throw "Unexpected literal at position " + iValue; + } + iValue++; + }; + + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + checkLiteral(); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + day = getNumber("d"); + break; + case "D": + getName("D", dayNamesShort, dayNames); + break; + case "o": + doy = getNumber("o"); + break; + case "m": + month = getNumber("m"); + break; + case "M": + month = getName("M", monthNamesShort, monthNames); + break; + case "y": + year = getNumber("y"); + break; + case "@": + date = new Date(getNumber("@")); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "!": + date = new Date((getNumber("!") - this._ticksTo1970) / 10000); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if (lookAhead("'")){ + checkLiteral(); + } else { + literal = true; + } + break; + default: + checkLiteral(); + } + } + } + + if (iValue < value.length){ + extra = value.substr(iValue); + if (!/^\s+/.test(extra)) { + throw "Extra/unparsed characters found in date: " + extra; + } + } + + if (year === -1) { + year = new Date().getFullYear(); + } else if (year < 100) { + year += new Date().getFullYear() - new Date().getFullYear() % 100 + + (year <= shortYearCutoff ? 0 : -100); + } + + if (doy > -1) { + month = 1; + day = doy; + do { + dim = this._getDaysInMonth(year, month - 1); + if (day <= dim) { + break; + } + month++; + day -= dim; + } while (true); + } + + date = this._daylightSavingAdjust(new Date(year, month - 1, day)); + if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { + throw "Invalid date"; // E.g. 31/02/00 + } + return date; + }, + + /* Standard date formats. */ + ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) + COOKIE: "D, dd M yy", + ISO_8601: "yy-mm-dd", + RFC_822: "D, d M y", + RFC_850: "DD, dd-M-y", + RFC_1036: "D, d M y", + RFC_1123: "D, d M yy", + RFC_2822: "D, d M yy", + RSS: "D, d M y", // RFC 822 + TICKS: "!", + TIMESTAMP: "@", + W3C: "yy-mm-dd", // ISO 8601 + + _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), + + /* Format a date object into a string value. + * The format can be combinations of the following: + * d - day of month (no leading zero) + * dd - day of month (two digit) + * o - day of year (no leading zeros) + * oo - day of year (three digit) + * D - day name short + * DD - day name long + * m - month of year (no leading zero) + * mm - month of year (two digit) + * M - month name short + * MM - month name long + * y - year (two digit) + * yy - year (four digit) + * @ - Unix timestamp (ms since 01/01/1970) + * ! - Windows ticks (100ns since 01/01/0001) + * "..." - literal text + * '' - single quote + * + * @param format string - the desired format of the date + * @param date Date - the date value to format + * @param settings Object - attributes include: + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return string - the date in the above format + */ + formatDate: function (format, date, settings) { + if (!date) { + return ""; + } + + var iFormat, + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Format a number, with leading zero if necessary + formatNumber = function(match, value, len) { + var num = "" + value; + if (lookAhead(match)) { + while (num.length < len) { + num = "0" + num; + } + } + return num; + }, + // Format a name, short or long as requested + formatName = function(match, value, shortNames, longNames) { + return (lookAhead(match) ? longNames[value] : shortNames[value]); + }, + output = "", + literal = false; + + if (date) { + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + output += format.charAt(iFormat); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + output += formatNumber("d", date.getDate(), 2); + break; + case "D": + output += formatName("D", date.getDay(), dayNamesShort, dayNames); + break; + case "o": + output += formatNumber("o", + Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3); + break; + case "m": + output += formatNumber("m", date.getMonth() + 1, 2); + break; + case "M": + output += formatName("M", date.getMonth(), monthNamesShort, monthNames); + break; + case "y": + output += (lookAhead("y") ? date.getFullYear() : + (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100); + break; + case "@": + output += date.getTime(); + break; + case "!": + output += date.getTime() * 10000 + this._ticksTo1970; + break; + case "'": + if (lookAhead("'")) { + output += "'"; + } else { + literal = true; + } + break; + default: + output += format.charAt(iFormat); + } + } + } + } + return output; + }, + + /* Extract all possible characters from the date format. */ + _possibleChars: function (format) { + var iFormat, + chars = "", + literal = false, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }; + + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + chars += format.charAt(iFormat); + } + } else { + switch (format.charAt(iFormat)) { + case "d": case "m": case "y": case "@": + chars += "0123456789"; + break; + case "D": case "M": + return null; // Accept anything + case "'": + if (lookAhead("'")) { + chars += "'"; + } else { + literal = true; + } + break; + default: + chars += format.charAt(iFormat); + } + } + } + return chars; + }, + + /* Get a setting value, defaulting if necessary. */ + _get: function(inst, name) { + return inst.settings[name] !== undefined ? + inst.settings[name] : this._defaults[name]; + }, + + /* Parse existing date and initialise date picker. */ + _setDateFromField: function(inst, noDefault) { + if (inst.input.val() === inst.lastVal) { + return; + } + + var dateFormat = this._get(inst, "dateFormat"), + dates = inst.lastVal = inst.input ? inst.input.val() : null, + defaultDate = this._getDefaultDate(inst), + date = defaultDate, + settings = this._getFormatConfig(inst); + + try { + date = this.parseDate(dateFormat, dates, settings) || defaultDate; + } catch (event) { + dates = (noDefault ? "" : dates); + } + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + inst.currentDay = (dates ? date.getDate() : 0); + inst.currentMonth = (dates ? date.getMonth() : 0); + inst.currentYear = (dates ? date.getFullYear() : 0); + this._adjustInstDate(inst); + }, + + /* Retrieve the default date shown on opening. */ + _getDefaultDate: function(inst) { + return this._restrictMinMax(inst, + this._determineDate(inst, this._get(inst, "defaultDate"), new Date())); + }, + + /* A date may be specified as an exact value or a relative one. */ + _determineDate: function(inst, date, defaultDate) { + var offsetNumeric = function(offset) { + var date = new Date(); + date.setDate(date.getDate() + offset); + return date; + }, + offsetString = function(offset) { + try { + return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + offset, $.datepicker._getFormatConfig(inst)); + } + catch (e) { + // Ignore + } + + var date = (offset.toLowerCase().match(/^c/) ? + $.datepicker._getDate(inst) : null) || new Date(), + year = date.getFullYear(), + month = date.getMonth(), + day = date.getDate(), + pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, + matches = pattern.exec(offset); + + while (matches) { + switch (matches[2] || "d") { + case "d" : case "D" : + day += parseInt(matches[1],10); break; + case "w" : case "W" : + day += parseInt(matches[1],10) * 7; break; + case "m" : case "M" : + month += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + case "y": case "Y" : + year += parseInt(matches[1],10); + day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); + break; + } + matches = pattern.exec(offset); + } + return new Date(year, month, day); + }, + newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) : + (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); + + newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate); + if (newDate) { + newDate.setHours(0); + newDate.setMinutes(0); + newDate.setSeconds(0); + newDate.setMilliseconds(0); + } + return this._daylightSavingAdjust(newDate); + }, + + /* Handle switch to/from daylight saving. + * Hours may be non-zero on daylight saving cut-over: + * > 12 when midnight changeover, but then cannot generate + * midnight datetime, so jump to 1AM, otherwise reset. + * @param date (Date) the date to check + * @return (Date) the corrected date + */ + _daylightSavingAdjust: function(date) { + if (!date) { + return null; + } + date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); + return date; + }, + + /* Set the date(s) directly. */ + _setDate: function(inst, date, noChange) { + var clear = !date, + origMonth = inst.selectedMonth, + origYear = inst.selectedYear, + newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); + + inst.selectedDay = inst.currentDay = newDate.getDate(); + inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); + inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); + if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) { + this._notifyChange(inst); + } + this._adjustInstDate(inst); + if (inst.input) { + inst.input.val(clear ? "" : this._formatDate(inst)); + } + }, + + /* Retrieve the date(s) directly. */ + _getDate: function(inst) { + var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null : + this._daylightSavingAdjust(new Date( + inst.currentYear, inst.currentMonth, inst.currentDay))); + return startDate; + }, + + /* Attach the onxxx handlers. These are declared statically so + * they work with static code transformers like Caja. + */ + _attachHandlers: function(inst) { + var stepMonths = this._get(inst, "stepMonths"), + id = "#" + inst.id.replace( /\\\\/g, "\\" ); + inst.dpDiv.find("[data-handler]").map(function () { + var handler = { + prev: function () { + window["DP_jQuery_" + dpuuid].datepicker._adjustDate(id, -stepMonths, "M"); + }, + next: function () { + window["DP_jQuery_" + dpuuid].datepicker._adjustDate(id, +stepMonths, "M"); + }, + hide: function () { + window["DP_jQuery_" + dpuuid].datepicker._hideDatepicker(); + }, + today: function () { + window["DP_jQuery_" + dpuuid].datepicker._gotoToday(id); + }, + selectDay: function () { + window["DP_jQuery_" + dpuuid].datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this); + return false; + }, + selectMonth: function () { + window["DP_jQuery_" + dpuuid].datepicker._selectMonthYear(id, this, "M"); + return false; + }, + selectYear: function () { + window["DP_jQuery_" + dpuuid].datepicker._selectMonthYear(id, this, "Y"); + return false; + } + }; + $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]); + }); + }, + + /* Generate the HTML for the current state of the date picker. */ + _generateHTML: function(inst) { + var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, + controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, + monthNames, monthNamesShort, beforeShowDay, showOtherMonths, + selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, + cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, + printDate, dRow, tbody, daySettings, otherMonth, unselectable, + tempDate = new Date(), + today = this._daylightSavingAdjust( + new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time + isRTL = this._get(inst, "isRTL"), + showButtonPanel = this._get(inst, "showButtonPanel"), + hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"), + navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"), + numMonths = this._getNumberOfMonths(inst), + showCurrentAtPos = this._get(inst, "showCurrentAtPos"), + stepMonths = this._get(inst, "stepMonths"), + isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1), + currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : + new Date(inst.currentYear, inst.currentMonth, inst.currentDay))), + minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + drawMonth = inst.drawMonth - showCurrentAtPos, + drawYear = inst.drawYear; + + if (drawMonth < 0) { + drawMonth += 12; + drawYear--; + } + if (maxDate) { + maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), + maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); + maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); + while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { + drawMonth--; + if (drawMonth < 0) { + drawMonth = 11; + drawYear--; + } + } + } + inst.drawMonth = drawMonth; + inst.drawYear = drawYear; + + prevText = this._get(inst, "prevText"); + prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), + this._getFormatConfig(inst))); + + prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? + "<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" + + " title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>" : + (hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+ prevText +"'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>")); + + nextText = this._get(inst, "nextText"); + nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, + this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), + this._getFormatConfig(inst))); + + next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? + "<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" + + " title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>" : + (hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+ nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>")); + + currentText = this._get(inst, "currentText"); + gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today); + currentText = (!navigationAsDateFormat ? currentText : + this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); + + controls = (!inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" + + this._get(inst, "closeText") + "</button>" : ""); + + buttonPanel = (showButtonPanel) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + (isRTL ? controls : "") + + (this._isInRange(inst, gotoDate) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" + + ">" + currentText + "</button>" : "") + (isRTL ? "" : controls) + "</div>" : ""; + + firstDay = parseInt(this._get(inst, "firstDay"),10); + firstDay = (isNaN(firstDay) ? 0 : firstDay); + + showWeek = this._get(inst, "showWeek"); + dayNames = this._get(inst, "dayNames"); + dayNamesMin = this._get(inst, "dayNamesMin"); + monthNames = this._get(inst, "monthNames"); + monthNamesShort = this._get(inst, "monthNamesShort"); + beforeShowDay = this._get(inst, "beforeShowDay"); + showOtherMonths = this._get(inst, "showOtherMonths"); + selectOtherMonths = this._get(inst, "selectOtherMonths"); + defaultDate = this._getDefaultDate(inst); + html = ""; + dow; + for (row = 0; row < numMonths[0]; row++) { + group = ""; + this.maxRows = 4; + for (col = 0; col < numMonths[1]; col++) { + selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); + cornerClass = " ui-corner-all"; + calender = ""; + if (isMultiMonth) { + calender += "<div class='ui-datepicker-group"; + if (numMonths[1] > 1) { + switch (col) { + case 0: calender += " ui-datepicker-group-first"; + cornerClass = " ui-corner-" + (isRTL ? "right" : "left"); break; + case numMonths[1]-1: calender += " ui-datepicker-group-last"; + cornerClass = " ui-corner-" + (isRTL ? "left" : "right"); break; + default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break; + } + } + calender += "'>"; + } + calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" + + (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") + + (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") + + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, + row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers + "</div><table class='ui-datepicker-calendar'><thead>" + + "<tr>"; + thead = (showWeek ? "<th class='ui-datepicker-week-col'>" + this._get(inst, "weekHeader") + "</th>" : ""); + for (dow = 0; dow < 7; dow++) { // days of the week + day = (dow + firstDay) % 7; + thead += "<th" + ((dow + firstDay + 6) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "") + ">" + + "<span title='" + dayNames[day] + "'>" + dayNamesMin[day] + "</span></th>"; + } + calender += thead + "</tr></thead><tbody>"; + daysInMonth = this._getDaysInMonth(drawYear, drawMonth); + if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { + inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); + } + leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; + curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate + numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043) + this.maxRows = numRows; + printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); + for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows + calender += "<tr>"; + tbody = (!showWeek ? "" : "<td class='ui-datepicker-week-col'>" + + this._get(inst, "calculateWeek")(printDate) + "</td>"); + for (dow = 0; dow < 7; dow++) { // create date picker days + daySettings = (beforeShowDay ? + beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]); + otherMonth = (printDate.getMonth() !== drawMonth); + unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || + (minDate && printDate < minDate) || (maxDate && printDate > maxDate); + tbody += "<td class='" + + ((dow + firstDay + 6) % 7 >= 5 ? " ui-datepicker-week-end" : "") + // highlight weekends + (otherMonth ? " ui-datepicker-other-month" : "") + // highlight days from other months + ((printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent) || // user pressed key + (defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ? + // or defaultDate is current printedDate and defaultDate is selectedDate + " " + this._dayOverClass : "") + // highlight selected day + (unselectable ? " " + this._unselectableClass + " ui-state-disabled": "") + // highlight unselectable days + (otherMonth && !showOtherMonths ? "" : " " + daySettings[1] + // highlight custom dates + (printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "") + // highlight selected day + (printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "")) + "'" + // highlight today (if different) + ((!otherMonth || showOtherMonths) && daySettings[2] ? " title='" + daySettings[2].replace(/'/g, "'") + "'" : "") + // cell title + (unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'") + ">" + // actions + (otherMonth && !showOtherMonths ? " " : // display for other months + (unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" + + (printDate.getTime() === today.getTime() ? " ui-state-highlight" : "") + + (printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "") + // highlight selected day + (otherMonth ? " ui-priority-secondary" : "") + // distinguish dates from other months + "' href='#'>" + printDate.getDate() + "</a>")) + "</td>"; // display selectable date + printDate.setDate(printDate.getDate() + 1); + printDate = this._daylightSavingAdjust(printDate); + } + calender += tbody + "</tr>"; + } + drawMonth++; + if (drawMonth > 11) { + drawMonth = 0; + drawYear++; + } + calender += "</tbody></table>" + (isMultiMonth ? "</div>" + + ((numMonths[0] > 0 && col === numMonths[1]-1) ? "<div class='ui-datepicker-row-break'></div>" : "") : ""); + group += calender; + } + html += group; + } + html += buttonPanel; + inst._keyEvent = false; + return html; + }, + + /* Generate the month and year header. */ + _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, + secondary, monthNames, monthNamesShort) { + + var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, + changeMonth = this._get(inst, "changeMonth"), + changeYear = this._get(inst, "changeYear"), + showMonthAfterYear = this._get(inst, "showMonthAfterYear"), + html = "<div class='ui-datepicker-title'>", + monthHtml = ""; + + // month selection + if (secondary || !changeMonth) { + monthHtml += "<span class='ui-datepicker-month'>" + monthNames[drawMonth] + "</span>"; + } else { + inMinYear = (minDate && minDate.getFullYear() === drawYear); + inMaxYear = (maxDate && maxDate.getFullYear() === drawYear); + monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>"; + for ( month = 0; month < 12; month++) { + if ((!inMinYear || month >= minDate.getMonth()) && (!inMaxYear || month <= maxDate.getMonth())) { + monthHtml += "<option value='" + month + "'" + + (month === drawMonth ? " selected='selected'" : "") + + ">" + monthNamesShort[month] + "</option>"; + } + } + monthHtml += "</select>"; + } + + if (!showMonthAfterYear) { + html += monthHtml + (secondary || !(changeMonth && changeYear) ? " " : ""); + } + + // year selection + if ( !inst.yearshtml ) { + inst.yearshtml = ""; + if (secondary || !changeYear) { + html += "<span class='ui-datepicker-year'>" + drawYear + "</span>"; + } else { + // determine range of years to display + years = this._get(inst, "yearRange").split(":"); + thisYear = new Date().getFullYear(); + determineYear = function(value) { + var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) : + (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) : + parseInt(value, 10))); + return (isNaN(year) ? thisYear : year); + }; + year = determineYear(years[0]); + endYear = Math.max(year, determineYear(years[1] || "")); + year = (minDate ? Math.max(year, minDate.getFullYear()) : year); + endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); + inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>"; + for (; year <= endYear; year++) { + inst.yearshtml += "<option value='" + year + "'" + + (year === drawYear ? " selected='selected'" : "") + + ">" + year + "</option>"; + } + inst.yearshtml += "</select>"; + + html += inst.yearshtml; + inst.yearshtml = null; + } + } + + html += this._get(inst, "yearSuffix"); + if (showMonthAfterYear) { + html += (secondary || !(changeMonth && changeYear) ? " " : "") + monthHtml; + } + html += "</div>"; // Close datepicker_header + return html; + }, + + /* Adjust one of the date sub-fields. */ + _adjustInstDate: function(inst, offset, period) { + var year = inst.drawYear + (period === "Y" ? offset : 0), + month = inst.drawMonth + (period === "M" ? offset : 0), + day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0), + date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); + + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); + if (period === "M" || period === "Y") { + this._notifyChange(inst); + } + }, + + /* Ensure a date is within any min/max bounds. */ + _restrictMinMax: function(inst, date) { + var minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + newDate = (minDate && date < minDate ? minDate : date); + return (maxDate && newDate > maxDate ? maxDate : newDate); + }, + + /* Notify change of month/year. */ + _notifyChange: function(inst) { + var onChange = this._get(inst, "onChangeMonthYear"); + if (onChange) { + onChange.apply((inst.input ? inst.input[0] : null), + [inst.selectedYear, inst.selectedMonth + 1, inst]); + } + }, + + /* Determine the number of months to show. */ + _getNumberOfMonths: function(inst) { + var numMonths = this._get(inst, "numberOfMonths"); + return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths)); + }, + + /* Determine the current maximum date - ensure no time components are set. */ + _getMinMaxDate: function(inst, minMax) { + return this._determineDate(inst, this._get(inst, minMax + "Date"), null); + }, + + /* Find the number of days in a given month. */ + _getDaysInMonth: function(year, month) { + return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); + }, + + /* Find the day of the week of the first of a month. */ + _getFirstDayOfMonth: function(year, month) { + return new Date(year, month, 1).getDay(); + }, + + /* Determines if we should allow a "next/prev" month display change. */ + _canAdjustMonth: function(inst, offset, curYear, curMonth) { + var numMonths = this._getNumberOfMonths(inst), + date = this._daylightSavingAdjust(new Date(curYear, + curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); + + if (offset < 0) { + date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); + } + return this._isInRange(inst, date); + }, + + /* Is the given date in the accepted range? */ + _isInRange: function(inst, date) { + var yearSplit, currentYear, + minDate = this._getMinMaxDate(inst, "min"), + maxDate = this._getMinMaxDate(inst, "max"), + minYear = null, + maxYear = null, + years = this._get(inst, "yearRange"); + if (years){ + yearSplit = years.split(":"); + currentYear = new Date().getFullYear(); + minYear = parseInt(yearSplit[0], 10); + maxYear = parseInt(yearSplit[1], 10); + if ( yearSplit[0].match(/[+\-].*/) ) { + minYear += currentYear; + } + if ( yearSplit[1].match(/[+\-].*/) ) { + maxYear += currentYear; + } + } + + return ((!minDate || date.getTime() >= minDate.getTime()) && + (!maxDate || date.getTime() <= maxDate.getTime()) && + (!minYear || date.getFullYear() >= minYear) && + (!maxYear || date.getFullYear() <= maxYear)); + }, + + /* Provide the configuration settings for formatting/parsing. */ + _getFormatConfig: function(inst) { + var shortYearCutoff = this._get(inst, "shortYearCutoff"); + shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff : + new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); + return {shortYearCutoff: shortYearCutoff, + dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"), + monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")}; + }, + + /* Format the given date for display. */ + _formatDate: function(inst, day, month, year) { + if (!day) { + inst.currentDay = inst.selectedDay; + inst.currentMonth = inst.selectedMonth; + inst.currentYear = inst.selectedYear; + } + var date = (day ? (typeof day === "object" ? day : + this._daylightSavingAdjust(new Date(year, month, day))) : + this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); + return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst)); + } +}); + +/* + * Bind hover events for datepicker elements. + * Done via delegate so the binding only occurs once in the lifetime of the parent div. + * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. + */ +function bindHover(dpDiv) { + var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; + return dpDiv.delegate(selector, "mouseout", function() { + $(this).removeClass("ui-state-hover"); + if (this.className.indexOf("ui-datepicker-prev") !== -1) { + $(this).removeClass("ui-datepicker-prev-hover"); + } + if (this.className.indexOf("ui-datepicker-next") !== -1) { + $(this).removeClass("ui-datepicker-next-hover"); + } + }) + .delegate(selector, "mouseover", function(){ + if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) { + $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); + $(this).addClass("ui-state-hover"); + if (this.className.indexOf("ui-datepicker-prev") !== -1) { + $(this).addClass("ui-datepicker-prev-hover"); + } + if (this.className.indexOf("ui-datepicker-next") !== -1) { + $(this).addClass("ui-datepicker-next-hover"); + } + } + }); +} + +/* jQuery extend now ignores nulls! */ +function extendRemove(target, props) { + $.extend(target, props); + for (var name in props) { + if (props[name] == null) { + target[name] = props[name]; + } + } + return target; +} + +/* Invoke the datepicker functionality. + @param options string - a command, optionally followed by additional parameters or + Object - settings for attaching new datepicker functionality + @return jQuery object */ +$.fn.datepicker = function(options){ + + /* Verify an empty collection wasn't passed - Fixes #6976 */ + if ( !this.length ) { + return this; + } + + /* Initialise the date picker. */ + if (!$.datepicker.initialized) { + $(document).mousedown($.datepicker._checkExternalClick); + $.datepicker.initialized = true; + } + + /* Append datepicker main container to body if not exist. */ + if ($("#"+$.datepicker._mainDivId).length === 0) { + $("body").append($.datepicker.dpDiv); + } + + var otherArgs = Array.prototype.slice.call(arguments, 1); + if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) { + return $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this[0]].concat(otherArgs)); + } + if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") { + return $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this[0]].concat(otherArgs)); + } + return this.each(function() { + typeof options === "string" ? + $.datepicker["_" + options + "Datepicker"]. + apply($.datepicker, [this].concat(otherArgs)) : + $.datepicker._attachDatepicker(this, options); + }); +}; + +$.datepicker = new Datepicker(); // singleton instance +$.datepicker.initialized = false; +$.datepicker.uuid = new Date().getTime(); +$.datepicker.version = "1.10.2"; + +// Workaround for #4055 +// Add another global to avoid noConflict issues with inline event handlers +window["DP_jQuery_" + dpuuid] = $; + +})(jQuery); + +(function( $, undefined ) { + +var sizeRelatedOptions = { + buttons: true, + height: true, + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true, + width: true + }, + resizableRelatedOptions = { + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true + }; + +$.widget( "ui.dialog", { + version: "1.10.2", + options: { + appendTo: "body", + autoOpen: true, + buttons: [], + closeOnEscape: true, + closeText: "close", + dialogClass: "", + draggable: true, + hide: null, + height: "auto", + maxHeight: null, + maxWidth: null, + minHeight: 150, + minWidth: 150, + modal: false, + position: { + my: "center", + at: "center", + of: window, + collision: "fit", + // Ensure the titlebar is always visible + using: function( pos ) { + var topOffset = $( this ).css( pos ).offset().top; + if ( topOffset < 0 ) { + $( this ).css( "top", pos.top - topOffset ); + } + } + }, + resizable: true, + show: null, + title: null, + width: 300, + + // callbacks + beforeClose: null, + close: null, + drag: null, + dragStart: null, + dragStop: null, + focus: null, + open: null, + resize: null, + resizeStart: null, + resizeStop: null + }, + + _create: function() { + this.originalCss = { + display: this.element[0].style.display, + width: this.element[0].style.width, + minHeight: this.element[0].style.minHeight, + maxHeight: this.element[0].style.maxHeight, + height: this.element[0].style.height + }; + this.originalPosition = { + parent: this.element.parent(), + index: this.element.parent().children().index( this.element ) + }; + this.originalTitle = this.element.attr("title"); + this.options.title = this.options.title || this.originalTitle; + + this._createWrapper(); + + this.element + .show() + .removeAttr("title") + .addClass("ui-dialog-content ui-widget-content") + .appendTo( this.uiDialog ); + + this._createTitlebar(); + this._createButtonPane(); + + if ( this.options.draggable && $.fn.draggable ) { + this._makeDraggable(); + } + if ( this.options.resizable && $.fn.resizable ) { + this._makeResizable(); + } + + this._isOpen = false; + }, + + _init: function() { + if ( this.options.autoOpen ) { + this.open(); + } + }, + + _appendTo: function() { + var element = this.options.appendTo; + if ( element && (element.jquery || element.nodeType) ) { + return $( element ); + } + return this.document.find( element || "body" ).eq( 0 ); + }, + + _destroy: function() { + var next, + originalPosition = this.originalPosition; + + this._destroyOverlay(); + + this.element + .removeUniqueId() + .removeClass("ui-dialog-content ui-widget-content") + .css( this.originalCss ) + // Without detaching first, the following becomes really slow + .detach(); + + this.uiDialog.stop( true, true ).remove(); + + if ( this.originalTitle ) { + this.element.attr( "title", this.originalTitle ); + } + + next = originalPosition.parent.children().eq( originalPosition.index ); + // Don't try to place the dialog next to itself (#8613) + if ( next.length && next[0] !== this.element[0] ) { + next.before( this.element ); + } else { + originalPosition.parent.append( this.element ); + } + }, + + widget: function() { + return this.uiDialog; + }, + + disable: $.noop, + enable: $.noop, + + close: function( event ) { + var that = this; + + if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { + return; + } + + this._isOpen = false; + this._destroyOverlay(); + + if ( !this.opener.filter(":focusable").focus().length ) { + // Hiding a focused element doesn't trigger blur in WebKit + // so in case we have nothing to focus on, explicitly blur the active element + // https://bugs.webkit.org/show_bug.cgi?id=47182 + $( this.document[0].activeElement ).blur(); + } + + this._hide( this.uiDialog, this.options.hide, function() { + that._trigger( "close", event ); + }); + }, + + isOpen: function() { + return this._isOpen; + }, + + moveToTop: function() { + this._moveToTop(); + }, + + _moveToTop: function( event, silent ) { + var moved = !!this.uiDialog.nextAll(":visible").insertBefore( this.uiDialog ).length; + if ( moved && !silent ) { + this._trigger( "focus", event ); + } + return moved; + }, + + open: function() { + var that = this; + if ( this._isOpen ) { + if ( this._moveToTop() ) { + this._focusTabbable(); + } + return; + } + + this._isOpen = true; + this.opener = $( this.document[0].activeElement ); + + this._size(); + this._position(); + this._createOverlay(); + this._moveToTop( null, true ); + this._show( this.uiDialog, this.options.show, function() { + that._focusTabbable(); + that._trigger("focus"); + }); + + this._trigger("open"); + }, + + _focusTabbable: function() { + // Set focus to the first match: + // 1. First element inside the dialog matching [autofocus] + // 2. Tabbable element inside the content element + // 3. Tabbable element inside the buttonpane + // 4. The close button + // 5. The dialog itself + var hasFocus = this.element.find("[autofocus]"); + if ( !hasFocus.length ) { + hasFocus = this.element.find(":tabbable"); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialogButtonPane.find(":tabbable"); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialogTitlebarClose.filter(":tabbable"); + } + if ( !hasFocus.length ) { + hasFocus = this.uiDialog; + } + hasFocus.eq( 0 ).focus(); + }, + + _keepFocus: function( event ) { + function checkFocus() { + var activeElement = this.document[0].activeElement, + isActive = this.uiDialog[0] === activeElement || + $.contains( this.uiDialog[0], activeElement ); + if ( !isActive ) { + this._focusTabbable(); + } + } + event.preventDefault(); + checkFocus.call( this ); + // support: IE + // IE <= 8 doesn't prevent moving focus even with event.preventDefault() + // so we check again later + this._delay( checkFocus ); + }, + + _createWrapper: function() { + this.uiDialog = $("<div>") + .addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " + + this.options.dialogClass ) + .hide() + .attr({ + // Setting tabIndex makes the div focusable + tabIndex: -1, + role: "dialog" + }) + .appendTo( this._appendTo() ); + + this._on( this.uiDialog, { + keydown: function( event ) { + if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && + event.keyCode === $.ui.keyCode.ESCAPE ) { + event.preventDefault(); + this.close( event ); + return; + } + + // prevent tabbing out of dialogs + if ( event.keyCode !== $.ui.keyCode.TAB ) { + return; + } + var tabbables = this.uiDialog.find(":tabbable"), + first = tabbables.filter(":first"), + last = tabbables.filter(":last"); + + if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) { + first.focus( 1 ); + event.preventDefault(); + } else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) { + last.focus( 1 ); + event.preventDefault(); + } + }, + mousedown: function( event ) { + if ( this._moveToTop( event ) ) { + this._focusTabbable(); + } + } + }); + + // We assume that any existing aria-describedby attribute means + // that the dialog content is marked up properly + // otherwise we brute force the content as the description + if ( !this.element.find("[aria-describedby]").length ) { + this.uiDialog.attr({ + "aria-describedby": this.element.uniqueId().attr("id") + }); + } + }, + + _createTitlebar: function() { + var uiDialogTitle; + + this.uiDialogTitlebar = $("<div>") + .addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix") + .prependTo( this.uiDialog ); + this._on( this.uiDialogTitlebar, { + mousedown: function( event ) { + // Don't prevent click on close button (#8838) + // Focusing a dialog that is partially scrolled out of view + // causes the browser to scroll it into view, preventing the click event + if ( !$( event.target ).closest(".ui-dialog-titlebar-close") ) { + // Dialog isn't getting focus when dragging (#8063) + this.uiDialog.focus(); + } + } + }); + + this.uiDialogTitlebarClose = $("<button></button>") + .button({ + label: this.options.closeText, + icons: { + primary: "ui-icon-closethick" + }, + text: false + }) + .addClass("ui-dialog-titlebar-close") + .appendTo( this.uiDialogTitlebar ); + this._on( this.uiDialogTitlebarClose, { + click: function( event ) { + event.preventDefault(); + this.close( event ); + } + }); + + uiDialogTitle = $("<span>") + .uniqueId() + .addClass("ui-dialog-title") + .prependTo( this.uiDialogTitlebar ); + this._title( uiDialogTitle ); + + this.uiDialog.attr({ + "aria-labelledby": uiDialogTitle.attr("id") + }); + }, + + _title: function( title ) { + if ( !this.options.title ) { + title.html(" "); + } + title.text( this.options.title ); + }, + + _createButtonPane: function() { + this.uiDialogButtonPane = $("<div>") + .addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"); + + this.uiButtonSet = $("<div>") + .addClass("ui-dialog-buttonset") + .appendTo( this.uiDialogButtonPane ); + + this._createButtons(); + }, + + _createButtons: function() { + var that = this, + buttons = this.options.buttons; + + // if we already have a button pane, remove it + this.uiDialogButtonPane.remove(); + this.uiButtonSet.empty(); + + if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) { + this.uiDialog.removeClass("ui-dialog-buttons"); + return; + } + + $.each( buttons, function( name, props ) { + var click, buttonOptions; + props = $.isFunction( props ) ? + { click: props, text: name } : + props; + // Default to a non-submitting button + props = $.extend( { type: "button" }, props ); + // Change the context for the click callback to be the main element + click = props.click; + props.click = function() { + click.apply( that.element[0], arguments ); + }; + buttonOptions = { + icons: props.icons, + text: props.showText + }; + delete props.icons; + delete props.showText; + $( "<button></button>", props ) + .button( buttonOptions ) + .appendTo( that.uiButtonSet ); + }); + this.uiDialog.addClass("ui-dialog-buttons"); + this.uiDialogButtonPane.appendTo( this.uiDialog ); + }, + + _makeDraggable: function() { + var that = this, + options = this.options; + + function filteredUi( ui ) { + return { + position: ui.position, + offset: ui.offset + }; + } + + this.uiDialog.draggable({ + cancel: ".ui-dialog-content, .ui-dialog-titlebar-close", + handle: ".ui-dialog-titlebar", + containment: "document", + start: function( event, ui ) { + $( this ).addClass("ui-dialog-dragging"); + that._blockFrames(); + that._trigger( "dragStart", event, filteredUi( ui ) ); + }, + drag: function( event, ui ) { + that._trigger( "drag", event, filteredUi( ui ) ); + }, + stop: function( event, ui ) { + options.position = [ + ui.position.left - that.document.scrollLeft(), + ui.position.top - that.document.scrollTop() + ]; + $( this ).removeClass("ui-dialog-dragging"); + that._unblockFrames(); + that._trigger( "dragStop", event, filteredUi( ui ) ); + } + }); + }, + + _makeResizable: function() { + var that = this, + options = this.options, + handles = options.resizable, + // .ui-resizable has position: relative defined in the stylesheet + // but dialogs have to use absolute or fixed positioning + position = this.uiDialog.css("position"), + resizeHandles = typeof handles === "string" ? + handles : + "n,e,s,w,se,sw,ne,nw"; + + function filteredUi( ui ) { + return { + originalPosition: ui.originalPosition, + originalSize: ui.originalSize, + position: ui.position, + size: ui.size + }; + } + + this.uiDialog.resizable({ + cancel: ".ui-dialog-content", + containment: "document", + alsoResize: this.element, + maxWidth: options.maxWidth, + maxHeight: options.maxHeight, + minWidth: options.minWidth, + minHeight: this._minHeight(), + handles: resizeHandles, + start: function( event, ui ) { + $( this ).addClass("ui-dialog-resizing"); + that._blockFrames(); + that._trigger( "resizeStart", event, filteredUi( ui ) ); + }, + resize: function( event, ui ) { + that._trigger( "resize", event, filteredUi( ui ) ); + }, + stop: function( event, ui ) { + options.height = $( this ).height(); + options.width = $( this ).width(); + $( this ).removeClass("ui-dialog-resizing"); + that._unblockFrames(); + that._trigger( "resizeStop", event, filteredUi( ui ) ); + } + }) + .css( "position", position ); + }, + + _minHeight: function() { + var options = this.options; + + return options.height === "auto" ? + options.minHeight : + Math.min( options.minHeight, options.height ); + }, + + _position: function() { + // Need to show the dialog to get the actual offset in the position plugin + var isVisible = this.uiDialog.is(":visible"); + if ( !isVisible ) { + this.uiDialog.show(); + } + this.uiDialog.position( this.options.position ); + if ( !isVisible ) { + this.uiDialog.hide(); + } + }, + + _setOptions: function( options ) { + var that = this, + resize = false, + resizableOptions = {}; + + $.each( options, function( key, value ) { + that._setOption( key, value ); + + if ( key in sizeRelatedOptions ) { + resize = true; + } + if ( key in resizableRelatedOptions ) { + resizableOptions[ key ] = value; + } + }); + + if ( resize ) { + this._size(); + this._position(); + } + if ( this.uiDialog.is(":data(ui-resizable)") ) { + this.uiDialog.resizable( "option", resizableOptions ); + } + }, + + _setOption: function( key, value ) { + /*jshint maxcomplexity:15*/ + var isDraggable, isResizable, + uiDialog = this.uiDialog; + + if ( key === "dialogClass" ) { + uiDialog + .removeClass( this.options.dialogClass ) + .addClass( value ); + } + + if ( key === "disabled" ) { + return; + } + + this._super( key, value ); + + if ( key === "appendTo" ) { + this.uiDialog.appendTo( this._appendTo() ); + } + + if ( key === "buttons" ) { + this._createButtons(); + } + + if ( key === "closeText" ) { + this.uiDialogTitlebarClose.button({ + // Ensure that we always pass a string + label: "" + value + }); + } + + if ( key === "draggable" ) { + isDraggable = uiDialog.is(":data(ui-draggable)"); + if ( isDraggable && !value ) { + uiDialog.draggable("destroy"); + } + + if ( !isDraggable && value ) { + this._makeDraggable(); + } + } + + if ( key === "position" ) { + this._position(); + } + + if ( key === "resizable" ) { + // currently resizable, becoming non-resizable + isResizable = uiDialog.is(":data(ui-resizable)"); + if ( isResizable && !value ) { + uiDialog.resizable("destroy"); + } + + // currently resizable, changing handles + if ( isResizable && typeof value === "string" ) { + uiDialog.resizable( "option", "handles", value ); + } + + // currently non-resizable, becoming resizable + if ( !isResizable && value !== false ) { + this._makeResizable(); + } + } + + if ( key === "title" ) { + this._title( this.uiDialogTitlebar.find(".ui-dialog-title") ); + } + }, + + _size: function() { + // If the user has resized the dialog, the .ui-dialog and .ui-dialog-content + // divs will both have width and height set, so we need to reset them + var nonContentHeight, minContentHeight, maxContentHeight, + options = this.options; + + // Reset content sizing + this.element.show().css({ + width: "auto", + minHeight: 0, + maxHeight: "none", + height: 0 + }); + + if ( options.minWidth > options.width ) { + options.width = options.minWidth; + } + + // reset wrapper sizing + // determine the height of all the non-content elements + nonContentHeight = this.uiDialog.css({ + height: "auto", + width: options.width + }) + .outerHeight(); + minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); + maxContentHeight = typeof options.maxHeight === "number" ? + Math.max( 0, options.maxHeight - nonContentHeight ) : + "none"; + + if ( options.height === "auto" ) { + this.element.css({ + minHeight: minContentHeight, + maxHeight: maxContentHeight, + height: "auto" + }); + } else { + this.element.height( Math.max( 0, options.height - nonContentHeight ) ); + } + + if (this.uiDialog.is(":data(ui-resizable)") ) { + this.uiDialog.resizable( "option", "minHeight", this._minHeight() ); + } + }, + + _blockFrames: function() { + this.iframeBlocks = this.document.find( "iframe" ).map(function() { + var iframe = $( this ); + + return $( "<div>" ) + .css({ + position: "absolute", + width: iframe.outerWidth(), + height: iframe.outerHeight() + }) + .appendTo( iframe.parent() ) + .offset( iframe.offset() )[0]; + }); + }, + + _unblockFrames: function() { + if ( this.iframeBlocks ) { + this.iframeBlocks.remove(); + delete this.iframeBlocks; + } + }, + + _allowInteraction: function( event ) { + if ( $( event.target ).closest(".ui-dialog").length ) { + return true; + } + + // TODO: Remove hack when datepicker implements + // the .ui-front logic (#8989) + return !!$( event.target ).closest(".ui-datepicker").length; + }, + + _createOverlay: function() { + if ( !this.options.modal ) { + return; + } + + var that = this, + widgetFullName = this.widgetFullName; + if ( !$.ui.dialog.overlayInstances ) { + // Prevent use of anchors and inputs. + // We use a delay in case the overlay is created from an + // event that we're going to be cancelling. (#2804) + this._delay(function() { + // Handle .dialog().dialog("close") (#4065) + if ( $.ui.dialog.overlayInstances ) { + this.document.bind( "focusin.dialog", function( event ) { + if ( !that._allowInteraction( event ) ) { + event.preventDefault(); + $(".ui-dialog:visible:last .ui-dialog-content") + .data( widgetFullName )._focusTabbable(); + } + }); + } + }); + } + + this.overlay = $("<div>") + .addClass("ui-widget-overlay ui-front") + .appendTo( this._appendTo() ); + this._on( this.overlay, { + mousedown: "_keepFocus" + }); + $.ui.dialog.overlayInstances++; + }, + + _destroyOverlay: function() { + if ( !this.options.modal ) { + return; + } + + if ( this.overlay ) { + $.ui.dialog.overlayInstances--; + + if ( !$.ui.dialog.overlayInstances ) { + this.document.unbind( "focusin.dialog" ); + } + this.overlay.remove(); + this.overlay = null; + } + } +}); + +$.ui.dialog.overlayInstances = 0; + +// DEPRECATED +if ( $.uiBackCompat !== false ) { + // position option with array notation + // just override with old implementation + $.widget( "ui.dialog", $.ui.dialog, { + _position: function() { + var position = this.options.position, + myAt = [], + offset = [ 0, 0 ], + isVisible; + + if ( position ) { + if ( typeof position === "string" || (typeof position === "object" && "0" in position ) ) { + myAt = position.split ? position.split(" ") : [ position[0], position[1] ]; + if ( myAt.length === 1 ) { + myAt[1] = myAt[0]; + } + + $.each( [ "left", "top" ], function( i, offsetPosition ) { + if ( +myAt[ i ] === myAt[ i ] ) { + offset[ i ] = myAt[ i ]; + myAt[ i ] = offsetPosition; + } + }); + + position = { + my: myAt[0] + (offset[0] < 0 ? offset[0] : "+" + offset[0]) + " " + + myAt[1] + (offset[1] < 0 ? offset[1] : "+" + offset[1]), + at: myAt.join(" ") + }; + } + + position = $.extend( {}, $.ui.dialog.prototype.options.position, position ); + } else { + position = $.ui.dialog.prototype.options.position; + } + + // need to show the dialog to get the actual offset in the position plugin + isVisible = this.uiDialog.is(":visible"); + if ( !isVisible ) { + this.uiDialog.show(); + } + this.uiDialog.position( position ); + if ( !isVisible ) { + this.uiDialog.hide(); + } + } + }); +} + +}( jQuery ) ); + +(function( $, undefined ) { + +var rvertical = /up|down|vertical/, + rpositivemotion = /up|left|vertical|horizontal/; + +$.effects.effect.blind = function( o, done ) { + // Create element + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "hide" ), + direction = o.direction || "up", + vertical = rvertical.test( direction ), + ref = vertical ? "height" : "width", + ref2 = vertical ? "top" : "left", + motion = rpositivemotion.test( direction ), + animation = {}, + show = mode === "show", + wrapper, distance, margin; + + // if already wrapped, the wrapper's properties are my property. #6245 + if ( el.parent().is( ".ui-effects-wrapper" ) ) { + $.effects.save( el.parent(), props ); + } else { + $.effects.save( el, props ); + } + el.show(); + wrapper = $.effects.createWrapper( el ).css({ + overflow: "hidden" + }); + + distance = wrapper[ ref ](); + margin = parseFloat( wrapper.css( ref2 ) ) || 0; + + animation[ ref ] = show ? distance : 0; + if ( !motion ) { + el + .css( vertical ? "bottom" : "right", 0 ) + .css( vertical ? "top" : "left", "auto" ) + .css({ position: "absolute" }); + + animation[ ref2 ] = show ? margin : distance + margin; + } + + // start at 0 if we are showing + if ( show ) { + wrapper.css( ref, 0 ); + if ( ! motion ) { + wrapper.css( ref2, margin + distance ); + } + } + + // Animate + wrapper.animate( animation, { + duration: o.duration, + easing: o.easing, + queue: false, + complete: function() { + if ( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + } + }); + +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.bounce = function( o, done ) { + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + + // defaults: + mode = $.effects.setMode( el, o.mode || "effect" ), + hide = mode === "hide", + show = mode === "show", + direction = o.direction || "up", + distance = o.distance, + times = o.times || 5, + + // number of internal animations + anims = times * 2 + ( show || hide ? 1 : 0 ), + speed = o.duration / anims, + easing = o.easing, + + // utility: + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + motion = ( direction === "up" || direction === "left" ), + i, + upAnim, + downAnim, + + // we will need to re-assemble the queue to stack our animations in place + queue = el.queue(), + queuelen = queue.length; + + // Avoid touching opacity to prevent clearType and PNG issues in IE + if ( show || hide ) { + props.push( "opacity" ); + } + + $.effects.save( el, props ); + el.show(); + $.effects.createWrapper( el ); // Create Wrapper + + // default distance for the BIGGEST bounce is the outer Distance / 3 + if ( !distance ) { + distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; + } + + if ( show ) { + downAnim = { opacity: 1 }; + downAnim[ ref ] = 0; + + // if we are showing, force opacity 0 and set the initial position + // then do the "first" animation + el.css( "opacity", 0 ) + .css( ref, motion ? -distance * 2 : distance * 2 ) + .animate( downAnim, speed, easing ); + } + + // start at the smallest distance if we are hiding + if ( hide ) { + distance = distance / Math.pow( 2, times - 1 ); + } + + downAnim = {}; + downAnim[ ref ] = 0; + // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here + for ( i = 0; i < times; i++ ) { + upAnim = {}; + upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; + + el.animate( upAnim, speed, easing ) + .animate( downAnim, speed, easing ); + + distance = hide ? distance * 2 : distance / 2; + } + + // Last Bounce when Hiding + if ( hide ) { + upAnim = { opacity: 0 }; + upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; + + el.animate( upAnim, speed, easing ); + } + + el.queue(function() { + if ( hide ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + }); + + // inject all the animations we just queued to be first in line (after "inprogress") + if ( queuelen > 1) { + queue.splice.apply( queue, + [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); + } + el.dequeue(); + +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.clip = function( o, done ) { + // Create element + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "hide" ), + show = mode === "show", + direction = o.direction || "vertical", + vert = direction === "vertical", + size = vert ? "height" : "width", + position = vert ? "top" : "left", + animation = {}, + wrapper, animate, distance; + + // Save & Show + $.effects.save( el, props ); + el.show(); + + // Create Wrapper + wrapper = $.effects.createWrapper( el ).css({ + overflow: "hidden" + }); + animate = ( el[0].tagName === "IMG" ) ? wrapper : el; + distance = animate[ size ](); + + // Shift + if ( show ) { + animate.css( size, 0 ); + animate.css( position, distance / 2 ); + } + + // Create Animation Object: + animation[ size ] = show ? distance : 0; + animation[ position ] = show ? 0 : distance / 2; + + // Animate + animate.animate( animation, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( !show ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + } + }); + +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.drop = function( o, done ) { + + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "hide" ), + show = mode === "show", + direction = o.direction || "left", + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg", + animation = { + opacity: show ? 1 : 0 + }, + distance; + + // Adjust + $.effects.save( el, props ); + el.show(); + $.effects.createWrapper( el ); + + distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2; + + if ( show ) { + el + .css( "opacity", 0 ) + .css( ref, motion === "pos" ? -distance : distance ); + } + + // Animation + animation[ ref ] = ( show ? + ( motion === "pos" ? "+=" : "-=" ) : + ( motion === "pos" ? "-=" : "+=" ) ) + + distance; + + // Animate + el.animate( animation, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + } + }); +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.explode = function( o, done ) { + + var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3, + cells = rows, + el = $( this ), + mode = $.effects.setMode( el, o.mode || "hide" ), + show = mode === "show", + + // show and then visibility:hidden the element before calculating offset + offset = el.show().css( "visibility", "hidden" ).offset(), + + // width and height of a piece + width = Math.ceil( el.outerWidth() / cells ), + height = Math.ceil( el.outerHeight() / rows ), + pieces = [], + + // loop + i, j, left, top, mx, my; + + // children animate complete: + function childComplete() { + pieces.push( this ); + if ( pieces.length === rows * cells ) { + animComplete(); + } + } + + // clone the element for each row and cell. + for( i = 0; i < rows ; i++ ) { // ===> + top = offset.top + i * height; + my = i - ( rows - 1 ) / 2 ; + + for( j = 0; j < cells ; j++ ) { // ||| + left = offset.left + j * width; + mx = j - ( cells - 1 ) / 2 ; + + // Create a clone of the now hidden main element that will be absolute positioned + // within a wrapper div off the -left and -top equal to size of our pieces + el + .clone() + .appendTo( "body" ) + .wrap( "<div></div>" ) + .css({ + position: "absolute", + visibility: "visible", + left: -j * width, + top: -i * height + }) + + // select the wrapper - make it overflow: hidden and absolute positioned based on + // where the original was located +left and +top equal to the size of pieces + .parent() + .addClass( "ui-effects-explode" ) + .css({ + position: "absolute", + overflow: "hidden", + width: width, + height: height, + left: left + ( show ? mx * width : 0 ), + top: top + ( show ? my * height : 0 ), + opacity: show ? 0 : 1 + }).animate({ + left: left + ( show ? 0 : mx * width ), + top: top + ( show ? 0 : my * height ), + opacity: show ? 1 : 0 + }, o.duration || 500, o.easing, childComplete ); + } + } + + function animComplete() { + el.css({ + visibility: "visible" + }); + $( pieces ).remove(); + if ( !show ) { + el.hide(); + } + done(); + } +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.fade = function( o, done ) { + var el = $( this ), + mode = $.effects.setMode( el, o.mode || "toggle" ); + + el.animate({ + opacity: mode + }, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: done + }); +}; + +})( jQuery ); + +(function( $, undefined ) { + +$.effects.effect.fold = function( o, done ) { + + // Create element + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "hide" ), + show = mode === "show", + hide = mode === "hide", + size = o.size || 15, + percent = /([0-9]+)%/.exec( size ), + horizFirst = !!o.horizFirst, + widthFirst = show !== horizFirst, + ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ], + duration = o.duration / 2, + wrapper, distance, + animation1 = {}, + animation2 = {}; + + $.effects.save( el, props ); + el.show(); + + // Create Wrapper + wrapper = $.effects.createWrapper( el ).css({ + overflow: "hidden" + }); + distance = widthFirst ? + [ wrapper.width(), wrapper.height() ] : + [ wrapper.height(), wrapper.width() ]; + + if ( percent ) { + size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ]; + } + if ( show ) { + wrapper.css( horizFirst ? { + height: 0, + width: size + } : { + height: size, + width: 0 + }); + } + + // Animation + animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size; + animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0; + + // Animate + wrapper + .animate( animation1, duration, o.easing ) + .animate( animation2, duration, o.easing, function() { + if ( hide ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + }); + +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.highlight = function( o, done ) { + var elem = $( this ), + props = [ "backgroundImage", "backgroundColor", "opacity" ], + mode = $.effects.setMode( elem, o.mode || "show" ), + animation = { + backgroundColor: elem.css( "backgroundColor" ) + }; + + if (mode === "hide") { + animation.opacity = 0; + } + + $.effects.save( elem, props ); + + elem + .show() + .css({ + backgroundImage: "none", + backgroundColor: o.color || "#ffff99" + }) + .animate( animation, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( mode === "hide" ) { + elem.hide(); + } + $.effects.restore( elem, props ); + done(); + } + }); +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.pulsate = function( o, done ) { + var elem = $( this ), + mode = $.effects.setMode( elem, o.mode || "show" ), + show = mode === "show", + hide = mode === "hide", + showhide = ( show || mode === "hide" ), + + // showing or hiding leaves of the "last" animation + anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), + duration = o.duration / anims, + animateTo = 0, + queue = elem.queue(), + queuelen = queue.length, + i; + + if ( show || !elem.is(":visible")) { + elem.css( "opacity", 0 ).show(); + animateTo = 1; + } + + // anims - 1 opacity "toggles" + for ( i = 1; i < anims; i++ ) { + elem.animate({ + opacity: animateTo + }, duration, o.easing ); + animateTo = 1 - animateTo; + } + + elem.animate({ + opacity: animateTo + }, duration, o.easing); + + elem.queue(function() { + if ( hide ) { + elem.hide(); + } + done(); + }); + + // We just queued up "anims" animations, we need to put them next in the queue + if ( queuelen > 1 ) { + queue.splice.apply( queue, + [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); + } + elem.dequeue(); +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.puff = function( o, done ) { + var elem = $( this ), + mode = $.effects.setMode( elem, o.mode || "hide" ), + hide = mode === "hide", + percent = parseInt( o.percent, 10 ) || 150, + factor = percent / 100, + original = { + height: elem.height(), + width: elem.width(), + outerHeight: elem.outerHeight(), + outerWidth: elem.outerWidth() + }; + + $.extend( o, { + effect: "scale", + queue: false, + fade: true, + mode: mode, + complete: done, + percent: hide ? percent : 100, + from: hide ? + original : + { + height: original.height * factor, + width: original.width * factor, + outerHeight: original.outerHeight * factor, + outerWidth: original.outerWidth * factor + } + }); + + elem.effect( o ); +}; + +$.effects.effect.scale = function( o, done ) { + + // Create element + var el = $( this ), + options = $.extend( true, {}, o ), + mode = $.effects.setMode( el, o.mode || "effect" ), + percent = parseInt( o.percent, 10 ) || + ( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ), + direction = o.direction || "both", + origin = o.origin, + original = { + height: el.height(), + width: el.width(), + outerHeight: el.outerHeight(), + outerWidth: el.outerWidth() + }, + factor = { + y: direction !== "horizontal" ? (percent / 100) : 1, + x: direction !== "vertical" ? (percent / 100) : 1 + }; + + // We are going to pass this effect to the size effect: + options.effect = "size"; + options.queue = false; + options.complete = done; + + // Set default origin and restore for show/hide + if ( mode !== "effect" ) { + options.origin = origin || ["middle","center"]; + options.restore = true; + } + + options.from = o.from || ( mode === "show" ? { + height: 0, + width: 0, + outerHeight: 0, + outerWidth: 0 + } : original ); + options.to = { + height: original.height * factor.y, + width: original.width * factor.x, + outerHeight: original.outerHeight * factor.y, + outerWidth: original.outerWidth * factor.x + }; + + // Fade option to support puff + if ( options.fade ) { + if ( mode === "show" ) { + options.from.opacity = 0; + options.to.opacity = 1; + } + if ( mode === "hide" ) { + options.from.opacity = 1; + options.to.opacity = 0; + } + } + + // Animate + el.effect( options ); + +}; + +$.effects.effect.size = function( o, done ) { + + // Create element + var original, baseline, factor, + el = $( this ), + props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ], + + // Always restore + props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ], + + // Copy for children + props2 = [ "width", "height", "overflow" ], + cProps = [ "fontSize" ], + vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ], + hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ], + + // Set options + mode = $.effects.setMode( el, o.mode || "effect" ), + restore = o.restore || mode !== "effect", + scale = o.scale || "both", + origin = o.origin || [ "middle", "center" ], + position = el.css( "position" ), + props = restore ? props0 : props1, + zero = { + height: 0, + width: 0, + outerHeight: 0, + outerWidth: 0 + }; + + if ( mode === "show" ) { + el.show(); + } + original = { + height: el.height(), + width: el.width(), + outerHeight: el.outerHeight(), + outerWidth: el.outerWidth() + }; + + if ( o.mode === "toggle" && mode === "show" ) { + el.from = o.to || zero; + el.to = o.from || original; + } else { + el.from = o.from || ( mode === "show" ? zero : original ); + el.to = o.to || ( mode === "hide" ? zero : original ); + } + + // Set scaling factor + factor = { + from: { + y: el.from.height / original.height, + x: el.from.width / original.width + }, + to: { + y: el.to.height / original.height, + x: el.to.width / original.width + } + }; + + // Scale the css box + if ( scale === "box" || scale === "both" ) { + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + props = props.concat( vProps ); + el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from ); + el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to ); + } + + // Horizontal props scaling + if ( factor.from.x !== factor.to.x ) { + props = props.concat( hProps ); + el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from ); + el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to ); + } + } + + // Scale the content + if ( scale === "content" || scale === "both" ) { + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + props = props.concat( cProps ).concat( props2 ); + el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from ); + el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to ); + } + } + + $.effects.save( el, props ); + el.show(); + $.effects.createWrapper( el ); + el.css( "overflow", "hidden" ).css( el.from ); + + // Adjust + if (origin) { // Calculate baseline shifts + baseline = $.effects.getBaseline( origin, original ); + el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y; + el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x; + el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y; + el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x; + } + el.css( el.from ); // set top & left + + // Animate + if ( scale === "content" || scale === "both" ) { // Scale the children + + // Add margins/font-size + vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps); + hProps = hProps.concat([ "marginLeft", "marginRight" ]); + props2 = props0.concat(vProps).concat(hProps); + + el.find( "*[width]" ).each( function(){ + var child = $( this ), + c_original = { + height: child.height(), + width: child.width(), + outerHeight: child.outerHeight(), + outerWidth: child.outerWidth() + }; + if (restore) { + $.effects.save(child, props2); + } + + child.from = { + height: c_original.height * factor.from.y, + width: c_original.width * factor.from.x, + outerHeight: c_original.outerHeight * factor.from.y, + outerWidth: c_original.outerWidth * factor.from.x + }; + child.to = { + height: c_original.height * factor.to.y, + width: c_original.width * factor.to.x, + outerHeight: c_original.height * factor.to.y, + outerWidth: c_original.width * factor.to.x + }; + + // Vertical props scaling + if ( factor.from.y !== factor.to.y ) { + child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from ); + child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to ); + } + + // Horizontal props scaling + if ( factor.from.x !== factor.to.x ) { + child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from ); + child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to ); + } + + // Animate children + child.css( child.from ); + child.animate( child.to, o.duration, o.easing, function() { + + // Restore children + if ( restore ) { + $.effects.restore( child, props2 ); + } + }); + }); + } + + // Animate + el.animate( el.to, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( el.to.opacity === 0 ) { + el.css( "opacity", el.from.opacity ); + } + if( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + if ( !restore ) { + + // we need to calculate our new positioning based on the scaling + if ( position === "static" ) { + el.css({ + position: "relative", + top: el.to.top, + left: el.to.left + }); + } else { + $.each([ "top", "left" ], function( idx, pos ) { + el.css( pos, function( _, str ) { + var val = parseInt( str, 10 ), + toRef = idx ? el.to.left : el.to.top; + + // if original was "auto", recalculate the new value from wrapper + if ( str === "auto" ) { + return toRef + "px"; + } + + return val + toRef + "px"; + }); + }); + } + } + + $.effects.removeWrapper( el ); + done(); + } + }); + +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.shake = function( o, done ) { + + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "height", "width" ], + mode = $.effects.setMode( el, o.mode || "effect" ), + direction = o.direction || "left", + distance = o.distance || 20, + times = o.times || 3, + anims = times * 2 + 1, + speed = Math.round(o.duration/anims), + ref = (direction === "up" || direction === "down") ? "top" : "left", + positiveMotion = (direction === "up" || direction === "left"), + animation = {}, + animation1 = {}, + animation2 = {}, + i, + + // we will need to re-assemble the queue to stack our animations in place + queue = el.queue(), + queuelen = queue.length; + + $.effects.save( el, props ); + el.show(); + $.effects.createWrapper( el ); + + // Animation + animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance; + animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2; + animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2; + + // Animate + el.animate( animation, speed, o.easing ); + + // Shakes + for ( i = 1; i < times; i++ ) { + el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing ); + } + el + .animate( animation1, speed, o.easing ) + .animate( animation, speed / 2, o.easing ) + .queue(function() { + if ( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + }); + + // inject all the animations we just queued to be first in line (after "inprogress") + if ( queuelen > 1) { + queue.splice.apply( queue, + [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); + } + el.dequeue(); + +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.slide = function( o, done ) { + + // Create element + var el = $( this ), + props = [ "position", "top", "bottom", "left", "right", "width", "height" ], + mode = $.effects.setMode( el, o.mode || "show" ), + show = mode === "show", + direction = o.direction || "left", + ref = (direction === "up" || direction === "down") ? "top" : "left", + positiveMotion = (direction === "up" || direction === "left"), + distance, + animation = {}; + + // Adjust + $.effects.save( el, props ); + el.show(); + distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ); + + $.effects.createWrapper( el ).css({ + overflow: "hidden" + }); + + if ( show ) { + el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance ); + } + + // Animation + animation[ ref ] = ( show ? + ( positiveMotion ? "+=" : "-=") : + ( positiveMotion ? "-=" : "+=")) + + distance; + + // Animate + el.animate( animation, { + queue: false, + duration: o.duration, + easing: o.easing, + complete: function() { + if ( mode === "hide" ) { + el.hide(); + } + $.effects.restore( el, props ); + $.effects.removeWrapper( el ); + done(); + } + }); +}; + +})(jQuery); + +(function( $, undefined ) { + +$.effects.effect.transfer = function( o, done ) { + var elem = $( this ), + target = $( o.to ), + targetFixed = target.css( "position" ) === "fixed", + body = $("body"), + fixTop = targetFixed ? body.scrollTop() : 0, + fixLeft = targetFixed ? body.scrollLeft() : 0, + endPosition = target.offset(), + animation = { + top: endPosition.top - fixTop , + left: endPosition.left - fixLeft , + height: target.innerHeight(), + width: target.innerWidth() + }, + startPosition = elem.offset(), + transfer = $( "<div class='ui-effects-transfer'></div>" ) + .appendTo( document.body ) + .addClass( o.className ) + .css({ + top: startPosition.top - fixTop , + left: startPosition.left - fixLeft , + height: elem.innerHeight(), + width: elem.innerWidth(), + position: targetFixed ? "fixed" : "absolute" + }) + .animate( animation, o.duration, o.easing, function() { + transfer.remove(); + done(); + }); +}; + +})(jQuery); + +(function( $, undefined ) { + +$.widget( "ui.menu", { + version: "1.10.2", + defaultElement: "<ul>", + delay: 300, + options: { + icons: { + submenu: "ui-icon-carat-1-e" + }, + menus: "ul", + position: { + my: "left top", + at: "right top" + }, + role: "menu", + + // callbacks + blur: null, + focus: null, + select: null + }, + + _create: function() { + this.activeMenu = this.element; + // flag used to prevent firing of the click handler + // as the event bubbles up through nested menus + this.mouseHandled = false; + this.element + .uniqueId() + .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) + .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ) + .attr({ + role: this.options.role, + tabIndex: 0 + }) + // need to catch all clicks on disabled menu + // not possible through _on + .bind( "click" + this.eventNamespace, $.proxy(function( event ) { + if ( this.options.disabled ) { + event.preventDefault(); + } + }, this )); + + if ( this.options.disabled ) { + this.element + .addClass( "ui-state-disabled" ) + .attr( "aria-disabled", "true" ); + } + + this._on({ + // Prevent focus from sticking to links inside menu after clicking + // them (focus should always stay on UL during navigation). + "mousedown .ui-menu-item > a": function( event ) { + event.preventDefault(); + }, + "click .ui-state-disabled > a": function( event ) { + event.preventDefault(); + }, + "click .ui-menu-item:has(a)": function( event ) { + var target = $( event.target ).closest( ".ui-menu-item" ); + if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { + this.mouseHandled = true; + + this.select( event ); + // Open submenu on click + if ( target.has( ".ui-menu" ).length ) { + this.expand( event ); + } else if ( !this.element.is( ":focus" ) ) { + // Redirect focus to the menu + this.element.trigger( "focus", [ true ] ); + + // If the active item is on the top level, let it stay active. + // Otherwise, blur the active item since it is no longer visible. + if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { + clearTimeout( this.timer ); + } + } + } + }, + "mouseenter .ui-menu-item": function( event ) { + var target = $( event.currentTarget ); + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border + target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" ); + this.focus( event, target ); + }, + mouseleave: "collapseAll", + "mouseleave .ui-menu": "collapseAll", + focus: function( event, keepActiveItem ) { + // If there's already an active item, keep it active + // If not, activate the first item + var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 ); + + if ( !keepActiveItem ) { + this.focus( event, item ); + } + }, + blur: function( event ) { + this._delay(function() { + if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { + this.collapseAll( event ); + } + }); + }, + keydown: "_keydown" + }); + + this.refresh(); + + // Clicks outside of a menu collapse any open menus + this._on( this.document, { + click: function( event ) { + if ( !$( event.target ).closest( ".ui-menu" ).length ) { + this.collapseAll( event ); + } + + // Reset the mouseHandled flag + this.mouseHandled = false; + } + }); + }, + + _destroy: function() { + // Destroy (sub)menus + this.element + .removeAttr( "aria-activedescendant" ) + .find( ".ui-menu" ).addBack() + .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-disabled" ) + .removeUniqueId() + .show(); + + // Destroy menu items + this.element.find( ".ui-menu-item" ) + .removeClass( "ui-menu-item" ) + .removeAttr( "role" ) + .removeAttr( "aria-disabled" ) + .children( "a" ) + .removeUniqueId() + .removeClass( "ui-corner-all ui-state-hover" ) + .removeAttr( "tabIndex" ) + .removeAttr( "role" ) + .removeAttr( "aria-haspopup" ) + .children().each( function() { + var elem = $( this ); + if ( elem.data( "ui-menu-submenu-carat" ) ) { + elem.remove(); + } + }); + + // Destroy menu dividers + this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" ); + }, + + _keydown: function( event ) { + /*jshint maxcomplexity:20*/ + var match, prev, character, skip, regex, + preventDefault = true; + + function escape( value ) { + return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); + } + + switch ( event.keyCode ) { + case $.ui.keyCode.PAGE_UP: + this.previousPage( event ); + break; + case $.ui.keyCode.PAGE_DOWN: + this.nextPage( event ); + break; + case $.ui.keyCode.HOME: + this._move( "first", "first", event ); + break; + case $.ui.keyCode.END: + this._move( "last", "last", event ); + break; + case $.ui.keyCode.UP: + this.previous( event ); + break; + case $.ui.keyCode.DOWN: + this.next( event ); + break; + case $.ui.keyCode.LEFT: + this.collapse( event ); + break; + case $.ui.keyCode.RIGHT: + if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { + this.expand( event ); + } + break; + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + this._activate( event ); + break; + case $.ui.keyCode.ESCAPE: + this.collapse( event ); + break; + default: + preventDefault = false; + prev = this.previousFilter || ""; + character = String.fromCharCode( event.keyCode ); + skip = false; + + clearTimeout( this.filterTimer ); + + if ( character === prev ) { + skip = true; + } else { + character = prev + character; + } + + regex = new RegExp( "^" + escape( character ), "i" ); + match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { + return regex.test( $( this ).children( "a" ).text() ); + }); + match = skip && match.index( this.active.next() ) !== -1 ? + this.active.nextAll( ".ui-menu-item" ) : + match; + + // If no matches on the current filter, reset to the last character pressed + // to move down the menu to the first item that starts with that character + if ( !match.length ) { + character = String.fromCharCode( event.keyCode ); + regex = new RegExp( "^" + escape( character ), "i" ); + match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { + return regex.test( $( this ).children( "a" ).text() ); + }); + } + + if ( match.length ) { + this.focus( event, match ); + if ( match.length > 1 ) { + this.previousFilter = character; + this.filterTimer = this._delay(function() { + delete this.previousFilter; + }, 1000 ); + } else { + delete this.previousFilter; + } + } else { + delete this.previousFilter; + } + } + + if ( preventDefault ) { + event.preventDefault(); + } + }, + + _activate: function( event ) { + if ( !this.active.is( ".ui-state-disabled" ) ) { + if ( this.active.children( "a[aria-haspopup='true']" ).length ) { + this.expand( event ); + } else { + this.select( event ); + } + } + }, + + refresh: function() { + var menus, + icon = this.options.icons.submenu, + submenus = this.element.find( this.options.menus ); + + // Initialize nested menus + submenus.filter( ":not(.ui-menu)" ) + .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) + .hide() + .attr({ + role: this.options.role, + "aria-hidden": "true", + "aria-expanded": "false" + }) + .each(function() { + var menu = $( this ), + item = menu.prev( "a" ), + submenuCarat = $( "<span>" ) + .addClass( "ui-menu-icon ui-icon " + icon ) + .data( "ui-menu-submenu-carat", true ); + + item + .attr( "aria-haspopup", "true" ) + .prepend( submenuCarat ); + menu.attr( "aria-labelledby", item.attr( "id" ) ); + }); + + menus = submenus.add( this.element ); + + // Don't refresh list items that are already adapted + menus.children( ":not(.ui-menu-item):has(a)" ) + .addClass( "ui-menu-item" ) + .attr( "role", "presentation" ) + .children( "a" ) + .uniqueId() + .addClass( "ui-corner-all" ) + .attr({ + tabIndex: -1, + role: this._itemRole() + }); + + // Initialize unlinked menu-items containing spaces and/or dashes only as dividers + menus.children( ":not(.ui-menu-item)" ).each(function() { + var item = $( this ); + // hyphen, em dash, en dash + if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) { + item.addClass( "ui-widget-content ui-menu-divider" ); + } + }); + + // Add aria-disabled attribute to any disabled menu item + menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); + + // If the active item has been removed, blur the menu + if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + this.blur(); + } + }, + + _itemRole: function() { + return { + menu: "menuitem", + listbox: "option" + }[ this.options.role ]; + }, + + _setOption: function( key, value ) { + if ( key === "icons" ) { + this.element.find( ".ui-menu-icon" ) + .removeClass( this.options.icons.submenu ) + .addClass( value.submenu ); + } + this._super( key, value ); + }, + + focus: function( event, item ) { + var nested, focused; + this.blur( event, event && event.type === "focus" ); + + this._scrollIntoView( item ); + + this.active = item.first(); + focused = this.active.children( "a" ).addClass( "ui-state-focus" ); + // Only update aria-activedescendant if there's a role + // otherwise we assume focus is managed elsewhere + if ( this.options.role ) { + this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); + } + + // Highlight active parent menu item, if any + this.active + .parent() + .closest( ".ui-menu-item" ) + .children( "a:first" ) + .addClass( "ui-state-active" ); + + if ( event && event.type === "keydown" ) { + this._close(); + } else { + this.timer = this._delay(function() { + this._close(); + }, this.delay ); + } + + nested = item.children( ".ui-menu" ); + if ( nested.length && ( /^mouse/.test( event.type ) ) ) { + this._startOpening(nested); + } + this.activeMenu = item.parent(); + + this._trigger( "focus", event, { item: item } ); + }, + + _scrollIntoView: function( item ) { + var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + if ( this._hasScroll() ) { + borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; + paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; + offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; + scroll = this.activeMenu.scrollTop(); + elementHeight = this.activeMenu.height(); + itemHeight = item.height(); + + if ( offset < 0 ) { + this.activeMenu.scrollTop( scroll + offset ); + } else if ( offset + itemHeight > elementHeight ) { + this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); + } + } + }, + + blur: function( event, fromFocus ) { + if ( !fromFocus ) { + clearTimeout( this.timer ); + } + + if ( !this.active ) { + return; + } + + this.active.children( "a" ).removeClass( "ui-state-focus" ); + this.active = null; + + this._trigger( "blur", event, { item: this.active } ); + }, + + _startOpening: function( submenu ) { + clearTimeout( this.timer ); + + // Don't open if already open fixes a Firefox bug that caused a .5 pixel + // shift in the submenu position when mousing over the carat icon + if ( submenu.attr( "aria-hidden" ) !== "true" ) { + return; + } + + this.timer = this._delay(function() { + this._close(); + this._open( submenu ); + }, this.delay ); + }, + + _open: function( submenu ) { + var position = $.extend({ + of: this.active + }, this.options.position ); + + clearTimeout( this.timer ); + this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) + .hide() + .attr( "aria-hidden", "true" ); + + submenu + .show() + .removeAttr( "aria-hidden" ) + .attr( "aria-expanded", "true" ) + .position( position ); + }, + + collapseAll: function( event, all ) { + clearTimeout( this.timer ); + this.timer = this._delay(function() { + // If we were passed an event, look for the submenu that contains the event + var currentMenu = all ? this.element : + $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); + + // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway + if ( !currentMenu.length ) { + currentMenu = this.element; + } + + this._close( currentMenu ); + + this.blur( event ); + this.activeMenu = currentMenu; + }, this.delay ); + }, + + // With no arguments, closes the currently active menu - if nothing is active + // it closes all menus. If passed an argument, it will search for menus BELOW + _close: function( startMenu ) { + if ( !startMenu ) { + startMenu = this.active ? this.active.parent() : this.element; + } + + startMenu + .find( ".ui-menu" ) + .hide() + .attr( "aria-hidden", "true" ) + .attr( "aria-expanded", "false" ) + .end() + .find( "a.ui-state-active" ) + .removeClass( "ui-state-active" ); + }, + + collapse: function( event ) { + var newItem = this.active && + this.active.parent().closest( ".ui-menu-item", this.element ); + if ( newItem && newItem.length ) { + this._close(); + this.focus( event, newItem ); + } + }, + + expand: function( event ) { + var newItem = this.active && + this.active + .children( ".ui-menu " ) + .children( ".ui-menu-item" ) + .first(); + + if ( newItem && newItem.length ) { + this._open( newItem.parent() ); + + // Delay so Firefox will not hide activedescendant change in expanding submenu from AT + this._delay(function() { + this.focus( event, newItem ); + }); + } + }, + + next: function( event ) { + this._move( "next", "first", event ); + }, + + previous: function( event ) { + this._move( "prev", "last", event ); + }, + + isFirstItem: function() { + return this.active && !this.active.prevAll( ".ui-menu-item" ).length; + }, + + isLastItem: function() { + return this.active && !this.active.nextAll( ".ui-menu-item" ).length; + }, + + _move: function( direction, filter, event ) { + var next; + if ( this.active ) { + if ( direction === "first" || direction === "last" ) { + next = this.active + [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) + .eq( -1 ); + } else { + next = this.active + [ direction + "All" ]( ".ui-menu-item" ) + .eq( 0 ); + } + } + if ( !next || !next.length || !this.active ) { + next = this.activeMenu.children( ".ui-menu-item" )[ filter ](); + } + + this.focus( event, next ); + }, + + nextPage: function( event ) { + var item, base, height; + + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isLastItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.nextAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base - height < 0; + }); + + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.children( ".ui-menu-item" ) + [ !this.active ? "first" : "last" ]() ); + } + }, + + previousPage: function( event ) { + var item, base, height; + if ( !this.active ) { + this.next( event ); + return; + } + if ( this.isFirstItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.prevAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base + height > 0; + }); + + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() ); + } + }, + + _hasScroll: function() { + return this.element.outerHeight() < this.element.prop( "scrollHeight" ); + }, + + select: function( event ) { + // TODO: It should never be possible to not have an active item at this + // point, but the tests don't trigger mouseenter before click. + this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); + var ui = { item: this.active }; + if ( !this.active.has( ".ui-menu" ).length ) { + this.collapseAll( event, true ); + } + this._trigger( "select", event, ui ); + } +}); + +}( jQuery )); + +(function( $, undefined ) { + +$.ui = $.ui || {}; + +var cachedScrollbarWidth, + max = Math.max, + abs = Math.abs, + round = Math.round, + rhorizontal = /left|center|right/, + rvertical = /top|center|bottom/, + roffset = /[\+\-]\d+(\.[\d]+)?%?/, + rposition = /^\w+/, + rpercent = /%$/, + _position = $.fn.position; + +function getOffsets( offsets, width, height ) { + return [ + parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), + parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) + ]; +} + +function parseCss( element, property ) { + return parseInt( $.css( element, property ), 10 ) || 0; +} + +function getDimensions( elem ) { + var raw = elem[0]; + if ( raw.nodeType === 9 ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: 0, left: 0 } + }; + } + if ( $.isWindow( raw ) ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: elem.scrollTop(), left: elem.scrollLeft() } + }; + } + if ( raw.preventDefault ) { + return { + width: 0, + height: 0, + offset: { top: raw.pageY, left: raw.pageX } + }; + } + return { + width: elem.outerWidth(), + height: elem.outerHeight(), + offset: elem.offset() + }; +} + +$.position = { + scrollbarWidth: function() { + if ( cachedScrollbarWidth !== undefined ) { + return cachedScrollbarWidth; + } + var w1, w2, + div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ), + innerDiv = div.children()[0]; + + $( "body" ).append( div ); + w1 = innerDiv.offsetWidth; + div.css( "overflow", "scroll" ); + + w2 = innerDiv.offsetWidth; + + if ( w1 === w2 ) { + w2 = div[0].clientWidth; + } + + div.remove(); + + return (cachedScrollbarWidth = w1 - w2); + }, + getScrollInfo: function( within ) { + var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ), + overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ), + hasOverflowX = overflowX === "scroll" || + ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), + hasOverflowY = overflowY === "scroll" || + ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); + return { + width: hasOverflowY ? $.position.scrollbarWidth() : 0, + height: hasOverflowX ? $.position.scrollbarWidth() : 0 + }; + }, + getWithinInfo: function( element ) { + var withinElement = $( element || window ), + isWindow = $.isWindow( withinElement[0] ); + return { + element: withinElement, + isWindow: isWindow, + offset: withinElement.offset() || { left: 0, top: 0 }, + scrollLeft: withinElement.scrollLeft(), + scrollTop: withinElement.scrollTop(), + width: isWindow ? withinElement.width() : withinElement.outerWidth(), + height: isWindow ? withinElement.height() : withinElement.outerHeight() + }; + } +}; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, + target = $( options.of ), + within = $.position.getWithinInfo( options.within ), + scrollInfo = $.position.getScrollInfo( within ), + collision = ( options.collision || "flip" ).split( " " ), + offsets = {}; + + dimensions = getDimensions( target ); + if ( target[0].preventDefault ) { + // force left top to allow flipping + options.at = "left top"; + } + targetWidth = dimensions.width; + targetHeight = dimensions.height; + targetOffset = dimensions.offset; + // clone to reuse original targetOffset later + basePosition = $.extend( {}, targetOffset ); + + // force my and at to have valid horizontal and vertical positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[ this ] || "" ).split( " " ), + horizontalOffset, + verticalOffset; + + if ( pos.length === 1) { + pos = rhorizontal.test( pos[ 0 ] ) ? + pos.concat( [ "center" ] ) : + rvertical.test( pos[ 0 ] ) ? + [ "center" ].concat( pos ) : + [ "center", "center" ]; + } + pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; + pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; + + // calculate offsets + horizontalOffset = roffset.exec( pos[ 0 ] ); + verticalOffset = roffset.exec( pos[ 1 ] ); + offsets[ this ] = [ + horizontalOffset ? horizontalOffset[ 0 ] : 0, + verticalOffset ? verticalOffset[ 0 ] : 0 + ]; + + // reduce to just the positions without the offsets + options[ this ] = [ + rposition.exec( pos[ 0 ] )[ 0 ], + rposition.exec( pos[ 1 ] )[ 0 ] + ]; + }); + + // normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + if ( options.at[ 0 ] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[ 0 ] === "center" ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[ 1 ] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[ 1 ] === "center" ) { + basePosition.top += targetHeight / 2; + } + + atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); + basePosition.left += atOffset[ 0 ]; + basePosition.top += atOffset[ 1 ]; + + return this.each(function() { + var collisionPosition, using, + elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseCss( this, "marginLeft" ), + marginTop = parseCss( this, "marginTop" ), + collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, + collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, + position = $.extend( {}, basePosition ), + myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); + + if ( options.my[ 0 ] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[ 0 ] === "center" ) { + position.left -= elemWidth / 2; + } + + if ( options.my[ 1 ] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[ 1 ] === "center" ) { + position.top -= elemHeight / 2; + } + + position.left += myOffset[ 0 ]; + position.top += myOffset[ 1 ]; + + // if the browser doesn't support fractions, then round for consistent results + if ( !$.support.offsetFractions ) { + position.left = round( position.left ); + position.top = round( position.top ); + } + + collisionPosition = { + marginLeft: marginLeft, + marginTop: marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[ i ] ] ) { + $.ui.position[ collision[ i ] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], + my: options.my, + at: options.at, + within: within, + elem : elem + }); + } + }); + + if ( options.using ) { + // adds feedback as second argument to using callback, if present + using = function( props ) { + var left = targetOffset.left - position.left, + right = left + targetWidth - elemWidth, + top = targetOffset.top - position.top, + bottom = top + targetHeight - elemHeight, + feedback = { + target: { + element: target, + left: targetOffset.left, + top: targetOffset.top, + width: targetWidth, + height: targetHeight + }, + element: { + element: elem, + left: position.left, + top: position.top, + width: elemWidth, + height: elemHeight + }, + horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", + vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" + }; + if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { + feedback.horizontal = "center"; + } + if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { + feedback.vertical = "middle"; + } + if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { + feedback.important = "horizontal"; + } else { + feedback.important = "vertical"; + } + options.using.call( this, props, feedback ); + }; + } + + elem.offset( $.extend( position, { using: using } ) ); + }); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, + outerWidth = within.width, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight; + + // element is wider than within + if ( data.collisionWidth > outerWidth ) { + // element is initially over the left side of within + if ( overLeft > 0 && overRight <= 0 ) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; + position.left += overLeft - newOverRight; + // element is initially over right side of within + } else if ( overRight > 0 && overLeft <= 0 ) { + position.left = withinOffset; + // element is initially over both left and right sides of within + } else { + if ( overLeft > overRight ) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + // too far left -> align with left edge + } else if ( overLeft > 0 ) { + position.left += overLeft; + // too far right -> align with right edge + } else if ( overRight > 0 ) { + position.left -= overRight; + // adjust based on position and margin + } else { + position.left = max( position.left - collisionPosLeft, position.left ); + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollTop : within.offset.top, + outerHeight = data.within.height, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverBottom; + + // element is taller than within + if ( data.collisionHeight > outerHeight ) { + // element is initially over the top of within + if ( overTop > 0 && overBottom <= 0 ) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; + position.top += overTop - newOverBottom; + // element is initially over bottom of within + } else if ( overBottom > 0 && overTop <= 0 ) { + position.top = withinOffset; + // element is initially over both top and bottom of within + } else { + if ( overTop > overBottom ) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + // too far up -> align with top + } else if ( overTop > 0 ) { + position.top += overTop; + // too far down -> align with bottom edge + } else if ( overBottom > 0 ) { + position.top -= overBottom; + // adjust based on position and margin + } else { + position.top = max( position.top - collisionPosTop, position.top ); + } + } + }, + flip: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.offset.left + within.scrollLeft, + outerWidth = within.width, + offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - offsetLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + data.at[ 0 ] === "right" ? + -data.targetWidth : + 0, + offset = -2 * data.offset[ 0 ], + newOverRight, + newOverLeft; + + if ( overLeft < 0 ) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; + if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { + position.left += myOffset + atOffset + offset; + } + } + else if ( overRight > 0 ) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; + if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { + position.left += myOffset + atOffset + offset; + } + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.offset.top + within.scrollTop, + outerHeight = within.height, + offsetTop = within.isWindow ? within.scrollTop : within.offset.top, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - offsetTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, + top = data.my[ 1 ] === "top", + myOffset = top ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + data.at[ 1 ] === "bottom" ? + -data.targetHeight : + 0, + offset = -2 * data.offset[ 1 ], + newOverTop, + newOverBottom; + if ( overTop < 0 ) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; + if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) { + position.top += myOffset + atOffset + offset; + } + } + else if ( overBottom > 0 ) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; + if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) { + position.top += myOffset + atOffset + offset; + } + } + } + }, + flipfit: { + left: function() { + $.ui.position.flip.left.apply( this, arguments ); + $.ui.position.fit.left.apply( this, arguments ); + }, + top: function() { + $.ui.position.flip.top.apply( this, arguments ); + $.ui.position.fit.top.apply( this, arguments ); + } + } +}; + +// fraction support test +(function () { + var testElement, testElementParent, testElementStyle, offsetLeft, i, + body = document.getElementsByTagName( "body" )[ 0 ], + div = document.createElement( "div" ); + + //Create a "fake body" for testing based on method used in jQuery.support + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if ( body ) { + $.extend( testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for ( i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || document.documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + + div.style.cssText = "position: absolute; left: 10.7432222px;"; + + offsetLeft = $( div ).offset().left; + $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; + + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); +})(); + +}( jQuery ) ); + +(function( $, undefined ) { + +$.widget( "ui.progressbar", { + version: "1.10.2", + options: { + max: 100, + value: 0, + + change: null, + complete: null + }, + + min: 0, + + _create: function() { + // Constrain initial value + this.oldValue = this.options.value = this._constrainedValue(); + + this.element + .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) + .attr({ + // Only set static values, aria-valuenow and aria-valuemax are + // set inside _refreshValue() + role: "progressbar", + "aria-valuemin": this.min + }); + + this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" ) + .appendTo( this.element ); + + this._refreshValue(); + }, + + _destroy: function() { + this.element + .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) + .removeAttr( "role" ) + .removeAttr( "aria-valuemin" ) + .removeAttr( "aria-valuemax" ) + .removeAttr( "aria-valuenow" ); + + this.valueDiv.remove(); + }, + + value: function( newValue ) { + if ( newValue === undefined ) { + return this.options.value; + } + + this.options.value = this._constrainedValue( newValue ); + this._refreshValue(); + }, + + _constrainedValue: function( newValue ) { + if ( newValue === undefined ) { + newValue = this.options.value; + } + + this.indeterminate = newValue === false; + + // sanitize value + if ( typeof newValue !== "number" ) { + newValue = 0; + } + + return this.indeterminate ? false : + Math.min( this.options.max, Math.max( this.min, newValue ) ); + }, + + _setOptions: function( options ) { + // Ensure "value" option is set after other values (like max) + var value = options.value; + delete options.value; + + this._super( options ); + + this.options.value = this._constrainedValue( value ); + this._refreshValue(); + }, + + _setOption: function( key, value ) { + if ( key === "max" ) { + // Don't allow a max less than min + value = Math.max( this.min, value ); + } + + this._super( key, value ); + }, + + _percentage: function() { + return this.indeterminate ? 100 : 100 * ( this.options.value - this.min ) / ( this.options.max - this.min ); + }, + + _refreshValue: function() { + var value = this.options.value, + percentage = this._percentage(); + + this.valueDiv + .toggle( this.indeterminate || value > this.min ) + .toggleClass( "ui-corner-right", value === this.options.max ) + .width( percentage.toFixed(0) + "%" ); + + this.element.toggleClass( "ui-progressbar-indeterminate", this.indeterminate ); + + if ( this.indeterminate ) { + this.element.removeAttr( "aria-valuenow" ); + if ( !this.overlayDiv ) { + this.overlayDiv = $( "<div class='ui-progressbar-overlay'></div>" ).appendTo( this.valueDiv ); + } + } else { + this.element.attr({ + "aria-valuemax": this.options.max, + "aria-valuenow": value + }); + if ( this.overlayDiv ) { + this.overlayDiv.remove(); + this.overlayDiv = null; + } + } + + if ( this.oldValue !== value ) { + this.oldValue = value; + this._trigger( "change" ); + } + if ( value === this.options.max ) { + this._trigger( "complete" ); + } + } +}); + +})( jQuery ); + +(function( $, undefined ) { + +// number of pages in a slider +// (how many times can you page up/down to go through the whole range) +var numPages = 5; + +$.widget( "ui.slider", $.ui.mouse, { + version: "1.10.2", + widgetEventPrefix: "slide", + + options: { + animate: false, + distance: 0, + max: 100, + min: 0, + orientation: "horizontal", + range: false, + step: 1, + value: 0, + values: null, + + // callbacks + change: null, + slide: null, + start: null, + stop: null + }, + + _create: function() { + this._keySliding = false; + this._mouseSliding = false; + this._animateOff = true; + this._handleIndex = null; + this._detectOrientation(); + this._mouseInit(); + + this.element + .addClass( "ui-slider" + + " ui-slider-" + this.orientation + + " ui-widget" + + " ui-widget-content" + + " ui-corner-all"); + + this._refresh(); + this._setOption( "disabled", this.options.disabled ); + + this._animateOff = false; + }, + + _refresh: function() { + this._createRange(); + this._createHandles(); + this._setupEvents(); + this._refreshValue(); + }, + + _createHandles: function() { + var i, handleCount, + options = this.options, + existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ), + handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>", + handles = []; + + handleCount = ( options.values && options.values.length ) || 1; + + if ( existingHandles.length > handleCount ) { + existingHandles.slice( handleCount ).remove(); + existingHandles = existingHandles.slice( 0, handleCount ); + } + + for ( i = existingHandles.length; i < handleCount; i++ ) { + handles.push( handle ); + } + + this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) ); + + this.handle = this.handles.eq( 0 ); + + this.handles.each(function( i ) { + $( this ).data( "ui-slider-handle-index", i ); + }); + }, + + _createRange: function() { + var options = this.options, + classes = ""; + + if ( options.range ) { + if ( options.range === true ) { + if ( !options.values ) { + options.values = [ this._valueMin(), this._valueMin() ]; + } else if ( options.values.length && options.values.length !== 2 ) { + options.values = [ options.values[0], options.values[0] ]; + } else if ( $.isArray( options.values ) ) { + options.values = options.values.slice(0); + } + } + + if ( !this.range || !this.range.length ) { + this.range = $( "<div></div>" ) + .appendTo( this.element ); + + classes = "ui-slider-range" + + // note: this isn't the most fittingly semantic framework class for this element, + // but worked best visually with a variety of themes + " ui-widget-header ui-corner-all"; + } else { + this.range.removeClass( "ui-slider-range-min ui-slider-range-max" ) + // Handle range switching from true to min/max + .css({ + "left": "", + "bottom": "" + }); + } + + this.range.addClass( classes + + ( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) ); + } else { + this.range = $([]); + } + }, + + _setupEvents: function() { + var elements = this.handles.add( this.range ).filter( "a" ); + this._off( elements ); + this._on( elements, this._handleEvents ); + this._hoverable( elements ); + this._focusable( elements ); + }, + + _destroy: function() { + this.handles.remove(); + this.range.remove(); + + this.element + .removeClass( "ui-slider" + + " ui-slider-horizontal" + + " ui-slider-vertical" + + " ui-widget" + + " ui-widget-content" + + " ui-corner-all" ); + + this._mouseDestroy(); + }, + + _mouseCapture: function( event ) { + var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle, + that = this, + o = this.options; + + if ( o.disabled ) { + return false; + } + + this.elementSize = { + width: this.element.outerWidth(), + height: this.element.outerHeight() + }; + this.elementOffset = this.element.offset(); + + position = { x: event.pageX, y: event.pageY }; + normValue = this._normValueFromMouse( position ); + distance = this._valueMax() - this._valueMin() + 1; + this.handles.each(function( i ) { + var thisDistance = Math.abs( normValue - that.values(i) ); + if (( distance > thisDistance ) || + ( distance === thisDistance && + (i === that._lastChangedValue || that.values(i) === o.min ))) { + distance = thisDistance; + closestHandle = $( this ); + index = i; + } + }); + + allowed = this._start( event, index ); + if ( allowed === false ) { + return false; + } + this._mouseSliding = true; + + this._handleIndex = index; + + closestHandle + .addClass( "ui-state-active" ) + .focus(); + + offset = closestHandle.offset(); + mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" ); + this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : { + left: event.pageX - offset.left - ( closestHandle.width() / 2 ), + top: event.pageY - offset.top - + ( closestHandle.height() / 2 ) - + ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) - + ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) + + ( parseInt( closestHandle.css("marginTop"), 10 ) || 0) + }; + + if ( !this.handles.hasClass( "ui-state-hover" ) ) { + this._slide( event, index, normValue ); + } + this._animateOff = true; + return true; + }, + + _mouseStart: function() { + return true; + }, + + _mouseDrag: function( event ) { + var position = { x: event.pageX, y: event.pageY }, + normValue = this._normValueFromMouse( position ); + + this._slide( event, this._handleIndex, normValue ); + + return false; + }, + + _mouseStop: function( event ) { + this.handles.removeClass( "ui-state-active" ); + this._mouseSliding = false; + + this._stop( event, this._handleIndex ); + this._change( event, this._handleIndex ); + + this._handleIndex = null; + this._clickOffset = null; + this._animateOff = false; + + return false; + }, + + _detectOrientation: function() { + this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal"; + }, + + _normValueFromMouse: function( position ) { + var pixelTotal, + pixelMouse, + percentMouse, + valueTotal, + valueMouse; + + if ( this.orientation === "horizontal" ) { + pixelTotal = this.elementSize.width; + pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 ); + } else { + pixelTotal = this.elementSize.height; + pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 ); + } + + percentMouse = ( pixelMouse / pixelTotal ); + if ( percentMouse > 1 ) { + percentMouse = 1; + } + if ( percentMouse < 0 ) { + percentMouse = 0; + } + if ( this.orientation === "vertical" ) { + percentMouse = 1 - percentMouse; + } + + valueTotal = this._valueMax() - this._valueMin(); + valueMouse = this._valueMin() + percentMouse * valueTotal; + + return this._trimAlignValue( valueMouse ); + }, + + _start: function( event, index ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + return this._trigger( "start", event, uiHash ); + }, + + _slide: function( event, index, newVal ) { + var otherVal, + newValues, + allowed; + + if ( this.options.values && this.options.values.length ) { + otherVal = this.values( index ? 0 : 1 ); + + if ( ( this.options.values.length === 2 && this.options.range === true ) && + ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) ) + ) { + newVal = otherVal; + } + + if ( newVal !== this.values( index ) ) { + newValues = this.values(); + newValues[ index ] = newVal; + // A slide can be canceled by returning false from the slide callback + allowed = this._trigger( "slide", event, { + handle: this.handles[ index ], + value: newVal, + values: newValues + } ); + otherVal = this.values( index ? 0 : 1 ); + if ( allowed !== false ) { + this.values( index, newVal, true ); + } + } + } else { + if ( newVal !== this.value() ) { + // A slide can be canceled by returning false from the slide callback + allowed = this._trigger( "slide", event, { + handle: this.handles[ index ], + value: newVal + } ); + if ( allowed !== false ) { + this.value( newVal ); + } + } + } + }, + + _stop: function( event, index ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + + this._trigger( "stop", event, uiHash ); + }, + + _change: function( event, index ) { + if ( !this._keySliding && !this._mouseSliding ) { + var uiHash = { + handle: this.handles[ index ], + value: this.value() + }; + if ( this.options.values && this.options.values.length ) { + uiHash.value = this.values( index ); + uiHash.values = this.values(); + } + + //store the last changed value index for reference when handles overlap + this._lastChangedValue = index; + + this._trigger( "change", event, uiHash ); + } + }, + + value: function( newValue ) { + if ( arguments.length ) { + this.options.value = this._trimAlignValue( newValue ); + this._refreshValue(); + this._change( null, 0 ); + return; + } + + return this._value(); + }, + + values: function( index, newValue ) { + var vals, + newValues, + i; + + if ( arguments.length > 1 ) { + this.options.values[ index ] = this._trimAlignValue( newValue ); + this._refreshValue(); + this._change( null, index ); + return; + } + + if ( arguments.length ) { + if ( $.isArray( arguments[ 0 ] ) ) { + vals = this.options.values; + newValues = arguments[ 0 ]; + for ( i = 0; i < vals.length; i += 1 ) { + vals[ i ] = this._trimAlignValue( newValues[ i ] ); + this._change( null, i ); + } + this._refreshValue(); + } else { + if ( this.options.values && this.options.values.length ) { + return this._values( index ); + } else { + return this.value(); + } + } + } else { + return this._values(); + } + }, + + _setOption: function( key, value ) { + var i, + valsLength = 0; + + if ( key === "range" && this.options.range === true ) { + if ( value === "min" ) { + this.options.value = this._values( 0 ); + this.options.values = null; + } else if ( value === "max" ) { + this.options.value = this._values( this.options.values.length-1 ); + this.options.values = null; + } + } + + if ( $.isArray( this.options.values ) ) { + valsLength = this.options.values.length; + } + + $.Widget.prototype._setOption.apply( this, arguments ); + + switch ( key ) { + case "orientation": + this._detectOrientation(); + this.element + .removeClass( "ui-slider-horizontal ui-slider-vertical" ) + .addClass( "ui-slider-" + this.orientation ); + this._refreshValue(); + break; + case "value": + this._animateOff = true; + this._refreshValue(); + this._change( null, 0 ); + this._animateOff = false; + break; + case "values": + this._animateOff = true; + this._refreshValue(); + for ( i = 0; i < valsLength; i += 1 ) { + this._change( null, i ); + } + this._animateOff = false; + break; + case "min": + case "max": + this._animateOff = true; + this._refreshValue(); + this._animateOff = false; + break; + case "range": + this._animateOff = true; + this._refresh(); + this._animateOff = false; + break; + } + }, + + //internal value getter + // _value() returns value trimmed by min and max, aligned by step + _value: function() { + var val = this.options.value; + val = this._trimAlignValue( val ); + + return val; + }, + + //internal values getter + // _values() returns array of values trimmed by min and max, aligned by step + // _values( index ) returns single value trimmed by min and max, aligned by step + _values: function( index ) { + var val, + vals, + i; + + if ( arguments.length ) { + val = this.options.values[ index ]; + val = this._trimAlignValue( val ); + + return val; + } else if ( this.options.values && this.options.values.length ) { + // .slice() creates a copy of the array + // this copy gets trimmed by min and max and then returned + vals = this.options.values.slice(); + for ( i = 0; i < vals.length; i+= 1) { + vals[ i ] = this._trimAlignValue( vals[ i ] ); + } + + return vals; + } else { + return []; + } + }, + + // returns the step-aligned value that val is closest to, between (inclusive) min and max + _trimAlignValue: function( val ) { + if ( val <= this._valueMin() ) { + return this._valueMin(); + } + if ( val >= this._valueMax() ) { + return this._valueMax(); + } + var step = ( this.options.step > 0 ) ? this.options.step : 1, + valModStep = (val - this._valueMin()) % step, + alignValue = val - valModStep; + + if ( Math.abs(valModStep) * 2 >= step ) { + alignValue += ( valModStep > 0 ) ? step : ( -step ); + } + + // Since JavaScript has problems with large floats, round + // the final value to 5 digits after the decimal point (see #4124) + return parseFloat( alignValue.toFixed(5) ); + }, + + _valueMin: function() { + return this.options.min; + }, + + _valueMax: function() { + return this.options.max; + }, + + _refreshValue: function() { + var lastValPercent, valPercent, value, valueMin, valueMax, + oRange = this.options.range, + o = this.options, + that = this, + animate = ( !this._animateOff ) ? o.animate : false, + _set = {}; + + if ( this.options.values && this.options.values.length ) { + this.handles.each(function( i ) { + valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100; + _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; + $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); + if ( that.options.range === true ) { + if ( that.orientation === "horizontal" ) { + if ( i === 0 ) { + that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate ); + } + if ( i === 1 ) { + that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } else { + if ( i === 0 ) { + that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate ); + } + if ( i === 1 ) { + that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } + } + lastValPercent = valPercent; + }); + } else { + value = this.value(); + valueMin = this._valueMin(); + valueMax = this._valueMax(); + valPercent = ( valueMax !== valueMin ) ? + ( value - valueMin ) / ( valueMax - valueMin ) * 100 : + 0; + _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; + this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); + + if ( oRange === "min" && this.orientation === "horizontal" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate ); + } + if ( oRange === "max" && this.orientation === "horizontal" ) { + this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + if ( oRange === "min" && this.orientation === "vertical" ) { + this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate ); + } + if ( oRange === "max" && this.orientation === "vertical" ) { + this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); + } + } + }, + + _handleEvents: { + keydown: function( event ) { + /*jshint maxcomplexity:25*/ + var allowed, curVal, newVal, step, + index = $( event.target ).data( "ui-slider-handle-index" ); + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + case $.ui.keyCode.END: + case $.ui.keyCode.PAGE_UP: + case $.ui.keyCode.PAGE_DOWN: + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + event.preventDefault(); + if ( !this._keySliding ) { + this._keySliding = true; + $( event.target ).addClass( "ui-state-active" ); + allowed = this._start( event, index ); + if ( allowed === false ) { + return; + } + } + break; + } + + step = this.options.step; + if ( this.options.values && this.options.values.length ) { + curVal = newVal = this.values( index ); + } else { + curVal = newVal = this.value(); + } + + switch ( event.keyCode ) { + case $.ui.keyCode.HOME: + newVal = this._valueMin(); + break; + case $.ui.keyCode.END: + newVal = this._valueMax(); + break; + case $.ui.keyCode.PAGE_UP: + newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) ); + break; + case $.ui.keyCode.PAGE_DOWN: + newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) ); + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.RIGHT: + if ( curVal === this._valueMax() ) { + return; + } + newVal = this._trimAlignValue( curVal + step ); + break; + case $.ui.keyCode.DOWN: + case $.ui.keyCode.LEFT: + if ( curVal === this._valueMin() ) { + return; + } + newVal = this._trimAlignValue( curVal - step ); + break; + } + + this._slide( event, index, newVal ); + }, + click: function( event ) { + event.preventDefault(); + }, + keyup: function( event ) { + var index = $( event.target ).data( "ui-slider-handle-index" ); + + if ( this._keySliding ) { + this._keySliding = false; + this._stop( event, index ); + this._change( event, index ); + $( event.target ).removeClass( "ui-state-active" ); + } + } + } + +}); + +}(jQuery)); + +(function( $ ) { + +function modifier( fn ) { + return function() { + var previous = this.element.val(); + fn.apply( this, arguments ); + this._refresh(); + if ( previous !== this.element.val() ) { + this._trigger( "change" ); + } + }; +} + +$.widget( "ui.spinner", { + version: "1.10.2", + defaultElement: "<input>", + widgetEventPrefix: "spin", + options: { + culture: null, + icons: { + down: "ui-icon-triangle-1-s", + up: "ui-icon-triangle-1-n" + }, + incremental: true, + max: null, + min: null, + numberFormat: null, + page: 10, + step: 1, + + change: null, + spin: null, + start: null, + stop: null + }, + + _create: function() { + // handle string values that need to be parsed + this._setOption( "max", this.options.max ); + this._setOption( "min", this.options.min ); + this._setOption( "step", this.options.step ); + + // format the value, but don't constrain + this._value( this.element.val(), true ); + + this._draw(); + this._on( this._events ); + this._refresh(); + + // turning off autocomplete prevents the browser from remembering the + // value when navigating through history, so we re-enable autocomplete + // if the page is unloaded before the widget is destroyed. #7790 + this._on( this.window, { + beforeunload: function() { + this.element.removeAttr( "autocomplete" ); + } + }); + }, + + _getCreateOptions: function() { + var options = {}, + element = this.element; + + $.each( [ "min", "max", "step" ], function( i, option ) { + var value = element.attr( option ); + if ( value !== undefined && value.length ) { + options[ option ] = value; + } + }); + + return options; + }, + + _events: { + keydown: function( event ) { + if ( this._start( event ) && this._keydown( event ) ) { + event.preventDefault(); + } + }, + keyup: "_stop", + focus: function() { + this.previous = this.element.val(); + }, + blur: function( event ) { + if ( this.cancelBlur ) { + delete this.cancelBlur; + return; + } + + this._stop(); + this._refresh(); + if ( this.previous !== this.element.val() ) { + this._trigger( "change", event ); + } + }, + mousewheel: function( event, delta ) { + if ( !delta ) { + return; + } + if ( !this.spinning && !this._start( event ) ) { + return false; + } + + this._spin( (delta > 0 ? 1 : -1) * this.options.step, event ); + clearTimeout( this.mousewheelTimer ); + this.mousewheelTimer = this._delay(function() { + if ( this.spinning ) { + this._stop( event ); + } + }, 100 ); + event.preventDefault(); + }, + "mousedown .ui-spinner-button": function( event ) { + var previous; + + // We never want the buttons to have focus; whenever the user is + // interacting with the spinner, the focus should be on the input. + // If the input is focused then this.previous is properly set from + // when the input first received focus. If the input is not focused + // then we need to set this.previous based on the value before spinning. + previous = this.element[0] === this.document[0].activeElement ? + this.previous : this.element.val(); + function checkFocus() { + var isActive = this.element[0] === this.document[0].activeElement; + if ( !isActive ) { + this.element.focus(); + this.previous = previous; + // support: IE + // IE sets focus asynchronously, so we need to check if focus + // moved off of the input because the user clicked on the button. + this._delay(function() { + this.previous = previous; + }); + } + } + + // ensure focus is on (or stays on) the text field + event.preventDefault(); + checkFocus.call( this ); + + // support: IE + // IE doesn't prevent moving focus even with event.preventDefault() + // so we set a flag to know when we should ignore the blur event + // and check (again) if focus moved off of the input. + this.cancelBlur = true; + this._delay(function() { + delete this.cancelBlur; + checkFocus.call( this ); + }); + + if ( this._start( event ) === false ) { + return; + } + + this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); + }, + "mouseup .ui-spinner-button": "_stop", + "mouseenter .ui-spinner-button": function( event ) { + // button will add ui-state-active if mouse was down while mouseleave and kept down + if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) { + return; + } + + if ( this._start( event ) === false ) { + return false; + } + this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); + }, + // TODO: do we really want to consider this a stop? + // shouldn't we just stop the repeater and wait until mouseup before + // we trigger the stop event? + "mouseleave .ui-spinner-button": "_stop" + }, + + _draw: function() { + var uiSpinner = this.uiSpinner = this.element + .addClass( "ui-spinner-input" ) + .attr( "autocomplete", "off" ) + .wrap( this._uiSpinnerHtml() ) + .parent() + // add buttons + .append( this._buttonHtml() ); + + this.element.attr( "role", "spinbutton" ); + + // button bindings + this.buttons = uiSpinner.find( ".ui-spinner-button" ) + .attr( "tabIndex", -1 ) + .button() + .removeClass( "ui-corner-all" ); + + // IE 6 doesn't understand height: 50% for the buttons + // unless the wrapper has an explicit height + if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) && + uiSpinner.height() > 0 ) { + uiSpinner.height( uiSpinner.height() ); + } + + // disable spinner if element was already disabled + if ( this.options.disabled ) { + this.disable(); + } + }, + + _keydown: function( event ) { + var options = this.options, + keyCode = $.ui.keyCode; + + switch ( event.keyCode ) { + case keyCode.UP: + this._repeat( null, 1, event ); + return true; + case keyCode.DOWN: + this._repeat( null, -1, event ); + return true; + case keyCode.PAGE_UP: + this._repeat( null, options.page, event ); + return true; + case keyCode.PAGE_DOWN: + this._repeat( null, -options.page, event ); + return true; + } + + return false; + }, + + _uiSpinnerHtml: function() { + return "<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>"; + }, + + _buttonHtml: function() { + return "" + + "<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" + + "<span class='ui-icon " + this.options.icons.up + "'>▲</span>" + + "</a>" + + "<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" + + "<span class='ui-icon " + this.options.icons.down + "'>▼</span>" + + "</a>"; + }, + + _start: function( event ) { + if ( !this.spinning && this._trigger( "start", event ) === false ) { + return false; + } + + if ( !this.counter ) { + this.counter = 1; + } + this.spinning = true; + return true; + }, + + _repeat: function( i, steps, event ) { + i = i || 500; + + clearTimeout( this.timer ); + this.timer = this._delay(function() { + this._repeat( 40, steps, event ); + }, i ); + + this._spin( steps * this.options.step, event ); + }, + + _spin: function( step, event ) { + var value = this.value() || 0; + + if ( !this.counter ) { + this.counter = 1; + } + + value = this._adjustValue( value + step * this._increment( this.counter ) ); + + if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) { + this._value( value ); + this.counter++; + } + }, + + _increment: function( i ) { + var incremental = this.options.incremental; + + if ( incremental ) { + return $.isFunction( incremental ) ? + incremental( i ) : + Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 ); + } + + return 1; + }, + + _precision: function() { + var precision = this._precisionOf( this.options.step ); + if ( this.options.min !== null ) { + precision = Math.max( precision, this._precisionOf( this.options.min ) ); + } + return precision; + }, + + _precisionOf: function( num ) { + var str = num.toString(), + decimal = str.indexOf( "." ); + return decimal === -1 ? 0 : str.length - decimal - 1; + }, + + _adjustValue: function( value ) { + var base, aboveMin, + options = this.options; + + // make sure we're at a valid step + // - find out where we are relative to the base (min or 0) + base = options.min !== null ? options.min : 0; + aboveMin = value - base; + // - round to the nearest step + aboveMin = Math.round(aboveMin / options.step) * options.step; + // - rounding is based on 0, so adjust back to our base + value = base + aboveMin; + + // fix precision from bad JS floating point math + value = parseFloat( value.toFixed( this._precision() ) ); + + // clamp the value + if ( options.max !== null && value > options.max) { + return options.max; + } + if ( options.min !== null && value < options.min ) { + return options.min; + } + + return value; + }, + + _stop: function( event ) { + if ( !this.spinning ) { + return; + } + + clearTimeout( this.timer ); + clearTimeout( this.mousewheelTimer ); + this.counter = 0; + this.spinning = false; + this._trigger( "stop", event ); + }, + + _setOption: function( key, value ) { + if ( key === "culture" || key === "numberFormat" ) { + var prevValue = this._parse( this.element.val() ); + this.options[ key ] = value; + this.element.val( this._format( prevValue ) ); + return; + } + + if ( key === "max" || key === "min" || key === "step" ) { + if ( typeof value === "string" ) { + value = this._parse( value ); + } + } + if ( key === "icons" ) { + this.buttons.first().find( ".ui-icon" ) + .removeClass( this.options.icons.up ) + .addClass( value.up ); + this.buttons.last().find( ".ui-icon" ) + .removeClass( this.options.icons.down ) + .addClass( value.down ); + } + + this._super( key, value ); + + if ( key === "disabled" ) { + if ( value ) { + this.element.prop( "disabled", true ); + this.buttons.button( "disable" ); + } else { + this.element.prop( "disabled", false ); + this.buttons.button( "enable" ); + } + } + }, + + _setOptions: modifier(function( options ) { + this._super( options ); + this._value( this.element.val() ); + }), + + _parse: function( val ) { + if ( typeof val === "string" && val !== "" ) { + val = window.Globalize && this.options.numberFormat ? + Globalize.parseFloat( val, 10, this.options.culture ) : +val; + } + return val === "" || isNaN( val ) ? null : val; + }, + + _format: function( value ) { + if ( value === "" ) { + return ""; + } + return window.Globalize && this.options.numberFormat ? + Globalize.format( value, this.options.numberFormat, this.options.culture ) : + value; + }, + + _refresh: function() { + this.element.attr({ + "aria-valuemin": this.options.min, + "aria-valuemax": this.options.max, + // TODO: what should we do with values that can't be parsed? + "aria-valuenow": this._parse( this.element.val() ) + }); + }, + + // update the value without triggering change + _value: function( value, allowAny ) { + var parsed; + if ( value !== "" ) { + parsed = this._parse( value ); + if ( parsed !== null ) { + if ( !allowAny ) { + parsed = this._adjustValue( parsed ); + } + value = this._format( parsed ); + } + } + this.element.val( value ); + this._refresh(); + }, + + _destroy: function() { + this.element + .removeClass( "ui-spinner-input" ) + .prop( "disabled", false ) + .removeAttr( "autocomplete" ) + .removeAttr( "role" ) + .removeAttr( "aria-valuemin" ) + .removeAttr( "aria-valuemax" ) + .removeAttr( "aria-valuenow" ); + this.uiSpinner.replaceWith( this.element ); + }, + + stepUp: modifier(function( steps ) { + this._stepUp( steps ); + }), + _stepUp: function( steps ) { + if ( this._start() ) { + this._spin( (steps || 1) * this.options.step ); + this._stop(); + } + }, + + stepDown: modifier(function( steps ) { + this._stepDown( steps ); + }), + _stepDown: function( steps ) { + if ( this._start() ) { + this._spin( (steps || 1) * -this.options.step ); + this._stop(); + } + }, + + pageUp: modifier(function( pages ) { + this._stepUp( (pages || 1) * this.options.page ); + }), + + pageDown: modifier(function( pages ) { + this._stepDown( (pages || 1) * this.options.page ); + }), + + value: function( newVal ) { + if ( !arguments.length ) { + return this._parse( this.element.val() ); + } + modifier( this._value ).call( this, newVal ); + }, + + widget: function() { + return this.uiSpinner; + } +}); + +}( jQuery ) ); + +(function( $, undefined ) { + +var tabId = 0, + rhash = /#.*$/; + +function getNextTabId() { + return ++tabId; +} + +function isLocal( anchor ) { + return anchor.hash.length > 1 && + decodeURIComponent( anchor.href.replace( rhash, "" ) ) === + decodeURIComponent( location.href.replace( rhash, "" ) ); +} + +$.widget( "ui.tabs", { + version: "1.10.2", + delay: 300, + options: { + active: null, + collapsible: false, + event: "click", + heightStyle: "content", + hide: null, + show: null, + + // callbacks + activate: null, + beforeActivate: null, + beforeLoad: null, + load: null + }, + + _create: function() { + var that = this, + options = this.options; + + this.running = false; + + this.element + .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ) + .toggleClass( "ui-tabs-collapsible", options.collapsible ) + // Prevent users from focusing disabled tabs via click + .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) { + if ( $( this ).is( ".ui-state-disabled" ) ) { + event.preventDefault(); + } + }) + // support: IE <9 + // Preventing the default action in mousedown doesn't prevent IE + // from focusing the element, so if the anchor gets focused, blur. + // We don't have to worry about focusing the previously focused + // element since clicking on a non-focusable element should focus + // the body anyway. + .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() { + if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { + this.blur(); + } + }); + + this._processTabs(); + options.active = this._initialActive(); + + // Take disabling tabs via class attribute from HTML + // into account and update option properly. + if ( $.isArray( options.disabled ) ) { + options.disabled = $.unique( options.disabled.concat( + $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { + return that.tabs.index( li ); + }) + ) ).sort(); + } + + // check for length avoids error when initializing empty list + if ( this.options.active !== false && this.anchors.length ) { + this.active = this._findActive( options.active ); + } else { + this.active = $(); + } + + this._refresh(); + + if ( this.active.length ) { + this.load( options.active ); + } + }, + + _initialActive: function() { + var active = this.options.active, + collapsible = this.options.collapsible, + locationHash = location.hash.substring( 1 ); + + if ( active === null ) { + // check the fragment identifier in the URL + if ( locationHash ) { + this.tabs.each(function( i, tab ) { + if ( $( tab ).attr( "aria-controls" ) === locationHash ) { + active = i; + return false; + } + }); + } + + // check for a tab marked active via a class + if ( active === null ) { + active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) ); + } + + // no active tab, set to false + if ( active === null || active === -1 ) { + active = this.tabs.length ? 0 : false; + } + } + + // handle numbers: negative, out of range + if ( active !== false ) { + active = this.tabs.index( this.tabs.eq( active ) ); + if ( active === -1 ) { + active = collapsible ? false : 0; + } + } + + // don't allow collapsible: false and active: false + if ( !collapsible && active === false && this.anchors.length ) { + active = 0; + } + + return active; + }, + + _getCreateEventData: function() { + return { + tab: this.active, + panel: !this.active.length ? $() : this._getPanelForTab( this.active ) + }; + }, + + _tabKeydown: function( event ) { + /*jshint maxcomplexity:15*/ + var focusedTab = $( this.document[0].activeElement ).closest( "li" ), + selectedIndex = this.tabs.index( focusedTab ), + goingForward = true; + + if ( this._handlePageNav( event ) ) { + return; + } + + switch ( event.keyCode ) { + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + selectedIndex++; + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.LEFT: + goingForward = false; + selectedIndex--; + break; + case $.ui.keyCode.END: + selectedIndex = this.anchors.length - 1; + break; + case $.ui.keyCode.HOME: + selectedIndex = 0; + break; + case $.ui.keyCode.SPACE: + // Activate only, no collapsing + event.preventDefault(); + clearTimeout( this.activating ); + this._activate( selectedIndex ); + return; + case $.ui.keyCode.ENTER: + // Toggle (cancel delayed activation, allow collapsing) + event.preventDefault(); + clearTimeout( this.activating ); + // Determine if we should collapse or activate + this._activate( selectedIndex === this.options.active ? false : selectedIndex ); + return; + default: + return; + } + + // Focus the appropriate tab, based on which key was pressed + event.preventDefault(); + clearTimeout( this.activating ); + selectedIndex = this._focusNextTab( selectedIndex, goingForward ); + + // Navigating with control key will prevent automatic activation + if ( !event.ctrlKey ) { + // Update aria-selected immediately so that AT think the tab is already selected. + // Otherwise AT may confuse the user by stating that they need to activate the tab, + // but the tab will already be activated by the time the announcement finishes. + focusedTab.attr( "aria-selected", "false" ); + this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" ); + + this.activating = this._delay(function() { + this.option( "active", selectedIndex ); + }, this.delay ); + } + }, + + _panelKeydown: function( event ) { + if ( this._handlePageNav( event ) ) { + return; + } + + // Ctrl+up moves focus to the current tab + if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) { + event.preventDefault(); + this.active.focus(); + } + }, + + // Alt+page up/down moves focus to the previous/next tab (and activates) + _handlePageNav: function( event ) { + if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) { + this._activate( this._focusNextTab( this.options.active - 1, false ) ); + return true; + } + if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) { + this._activate( this._focusNextTab( this.options.active + 1, true ) ); + return true; + } + }, + + _findNextTab: function( index, goingForward ) { + var lastTabIndex = this.tabs.length - 1; + + function constrain() { + if ( index > lastTabIndex ) { + index = 0; + } + if ( index < 0 ) { + index = lastTabIndex; + } + return index; + } + + while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) { + index = goingForward ? index + 1 : index - 1; + } + + return index; + }, + + _focusNextTab: function( index, goingForward ) { + index = this._findNextTab( index, goingForward ); + this.tabs.eq( index ).focus(); + return index; + }, + + _setOption: function( key, value ) { + if ( key === "active" ) { + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; + } + + if ( key === "disabled" ) { + // don't use the widget factory's disabled handling + this._setupDisabled( value ); + return; + } + + this._super( key, value); + + if ( key === "collapsible" ) { + this.element.toggleClass( "ui-tabs-collapsible", value ); + // Setting collapsible: false while collapsed; open first panel + if ( !value && this.options.active === false ) { + this._activate( 0 ); + } + } + + if ( key === "event" ) { + this._setupEvents( value ); + } + + if ( key === "heightStyle" ) { + this._setupHeightStyle( value ); + } + }, + + _tabId: function( tab ) { + return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId(); + }, + + _sanitizeSelector: function( hash ) { + return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : ""; + }, + + refresh: function() { + var options = this.options, + lis = this.tablist.children( ":has(a[href])" ); + + // get disabled tabs from class attribute from HTML + // this will get converted to a boolean if needed in _refresh() + options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) { + return lis.index( tab ); + }); + + this._processTabs(); + + // was collapsed or no tabs + if ( options.active === false || !this.anchors.length ) { + options.active = false; + this.active = $(); + // was active, but active tab is gone + } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) { + // all remaining tabs are disabled + if ( this.tabs.length === options.disabled.length ) { + options.active = false; + this.active = $(); + // activate previous tab + } else { + this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) ); + } + // was active, active tab still exists + } else { + // make sure active index is correct + options.active = this.tabs.index( this.active ); + } + + this._refresh(); + }, + + _refresh: function() { + this._setupDisabled( this.options.disabled ); + this._setupEvents( this.options.event ); + this._setupHeightStyle( this.options.heightStyle ); + + this.tabs.not( this.active ).attr({ + "aria-selected": "false", + tabIndex: -1 + }); + this.panels.not( this._getPanelForTab( this.active ) ) + .hide() + .attr({ + "aria-expanded": "false", + "aria-hidden": "true" + }); + + // Make sure one tab is in the tab order + if ( !this.active.length ) { + this.tabs.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active + .addClass( "ui-tabs-active ui-state-active" ) + .attr({ + "aria-selected": "true", + tabIndex: 0 + }); + this._getPanelForTab( this.active ) + .show() + .attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }); + } + }, + + _processTabs: function() { + var that = this; + + this.tablist = this._getList() + .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) + .attr( "role", "tablist" ); + + this.tabs = this.tablist.find( "> li:has(a[href])" ) + .addClass( "ui-state-default ui-corner-top" ) + .attr({ + role: "tab", + tabIndex: -1 + }); + + this.anchors = this.tabs.map(function() { + return $( "a", this )[ 0 ]; + }) + .addClass( "ui-tabs-anchor" ) + .attr({ + role: "presentation", + tabIndex: -1 + }); + + this.panels = $(); + + this.anchors.each(function( i, anchor ) { + var selector, panel, panelId, + anchorId = $( anchor ).uniqueId().attr( "id" ), + tab = $( anchor ).closest( "li" ), + originalAriaControls = tab.attr( "aria-controls" ); + + // inline tab + if ( isLocal( anchor ) ) { + selector = anchor.hash; + panel = that.element.find( that._sanitizeSelector( selector ) ); + // remote tab + } else { + panelId = that._tabId( tab ); + selector = "#" + panelId; + panel = that.element.find( selector ); + if ( !panel.length ) { + panel = that._createPanel( panelId ); + panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); + } + panel.attr( "aria-live", "polite" ); + } + + if ( panel.length) { + that.panels = that.panels.add( panel ); + } + if ( originalAriaControls ) { + tab.data( "ui-tabs-aria-controls", originalAriaControls ); + } + tab.attr({ + "aria-controls": selector.substring( 1 ), + "aria-labelledby": anchorId + }); + panel.attr( "aria-labelledby", anchorId ); + }); + + this.panels + .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) + .attr( "role", "tabpanel" ); + }, + + // allow overriding how to find the list for rare usage scenarios (#7715) + _getList: function() { + return this.element.find( "ol,ul" ).eq( 0 ); + }, + + _createPanel: function( id ) { + return $( "<div>" ) + .attr( "id", id ) + .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) + .data( "ui-tabs-destroy", true ); + }, + + _setupDisabled: function( disabled ) { + if ( $.isArray( disabled ) ) { + if ( !disabled.length ) { + disabled = false; + } else if ( disabled.length === this.anchors.length ) { + disabled = true; + } + } + + // disable tabs + for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) { + if ( disabled === true || $.inArray( i, disabled ) !== -1 ) { + $( li ) + .addClass( "ui-state-disabled" ) + .attr( "aria-disabled", "true" ); + } else { + $( li ) + .removeClass( "ui-state-disabled" ) + .removeAttr( "aria-disabled" ); + } + } + + this.options.disabled = disabled; + }, + + _setupEvents: function( event ) { + var events = { + click: function( event ) { + event.preventDefault(); + } + }; + if ( event ) { + $.each( event.split(" "), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); + } + + this._off( this.anchors.add( this.tabs ).add( this.panels ) ); + this._on( this.anchors, events ); + this._on( this.tabs, { keydown: "_tabKeydown" } ); + this._on( this.panels, { keydown: "_panelKeydown" } ); + + this._focusable( this.tabs ); + this._hoverable( this.tabs ); + }, + + _setupHeightStyle: function( heightStyle ) { + var maxHeight, + parent = this.element.parent(); + + if ( heightStyle === "fill" ) { + maxHeight = parent.height(); + maxHeight -= this.element.outerHeight() - this.element.height(); + + this.element.siblings( ":visible" ).each(function() { + var elem = $( this ), + position = elem.css( "position" ); + + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + }); + + this.element.children().not( this.panels ).each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); + + this.panels.each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.panels.each(function() { + maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); + }).height( maxHeight ); + } + }, + + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + anchor = $( event.currentTarget ), + tab = anchor.closest( "li" ), + clickedIsActive = tab[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : this._getPanelForTab( tab ), + toHide = !active.length ? $() : this._getPanelForTab( active ), + eventData = { + oldTab: active, + oldPanel: toHide, + newTab: collapsing ? $() : tab, + newPanel: toShow + }; + + event.preventDefault(); + + if ( tab.hasClass( "ui-state-disabled" ) || + // tab is already loading + tab.hasClass( "ui-tabs-loading" ) || + // can't switch durning an animation + this.running || + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } + + options.active = collapsing ? false : this.tabs.index( tab ); + + this.active = clickedIsActive ? $() : tab; + if ( this.xhr ) { + this.xhr.abort(); + } + + if ( !toHide.length && !toShow.length ) { + $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); + } + + if ( toShow.length ) { + this.load( this.tabs.index( tab ), event ); + } + this._toggle( event, eventData ); + }, + + // handles show/hide for selecting tabs + _toggle: function( event, eventData ) { + var that = this, + toShow = eventData.newPanel, + toHide = eventData.oldPanel; + + this.running = true; + + function complete() { + that.running = false; + that._trigger( "activate", event, eventData ); + } + + function show() { + eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); + + if ( toShow.length && that.options.show ) { + that._show( toShow, that.options.show, complete ); + } else { + toShow.show(); + complete(); + } + } + + // start out by hiding, then showing, then completing + if ( toHide.length && this.options.hide ) { + this._hide( toHide, this.options.hide, function() { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + show(); + }); + } else { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + toHide.hide(); + show(); + } + + toHide.attr({ + "aria-expanded": "false", + "aria-hidden": "true" + }); + eventData.oldTab.attr( "aria-selected", "false" ); + // If we're switching tabs, remove the old tab from the tab order. + // If we're opening from collapsed state, remove the previous tab from the tab order. + // If we're collapsing, then keep the collapsing tab in the tab order. + if ( toShow.length && toHide.length ) { + eventData.oldTab.attr( "tabIndex", -1 ); + } else if ( toShow.length ) { + this.tabs.filter(function() { + return $( this ).attr( "tabIndex" ) === 0; + }) + .attr( "tabIndex", -1 ); + } + + toShow.attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }); + eventData.newTab.attr({ + "aria-selected": "true", + tabIndex: 0 + }); + }, + + _activate: function( index ) { + var anchor, + active = this._findActive( index ); + + // trying to activate the already active panel + if ( active[ 0 ] === this.active[ 0 ] ) { + return; + } + + // trying to collapse, simulate a click on the current active header + if ( !active.length ) { + active = this.active; + } + + anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; + this._eventHandler({ + target: anchor, + currentTarget: anchor, + preventDefault: $.noop + }); + }, + + _findActive: function( index ) { + return index === false ? $() : this.tabs.eq( index ); + }, + + _getIndex: function( index ) { + // meta-function to give users option to provide a href string instead of a numerical index. + if ( typeof index === "string" ) { + index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) ); + } + + return index; + }, + + _destroy: function() { + if ( this.xhr ) { + this.xhr.abort(); + } + + this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" ); + + this.tablist + .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) + .removeAttr( "role" ); + + this.anchors + .removeClass( "ui-tabs-anchor" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeUniqueId(); + + this.tabs.add( this.panels ).each(function() { + if ( $.data( this, "ui-tabs-destroy" ) ) { + $( this ).remove(); + } else { + $( this ) + .removeClass( "ui-state-default ui-state-active ui-state-disabled " + + "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-live" ) + .removeAttr( "aria-busy" ) + .removeAttr( "aria-selected" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "role" ); + } + }); + + this.tabs.each(function() { + var li = $( this ), + prev = li.data( "ui-tabs-aria-controls" ); + if ( prev ) { + li + .attr( "aria-controls", prev ) + .removeData( "ui-tabs-aria-controls" ); + } else { + li.removeAttr( "aria-controls" ); + } + }); + + this.panels.show(); + + if ( this.options.heightStyle !== "content" ) { + this.panels.css( "height", "" ); + } + }, + + enable: function( index ) { + var disabled = this.options.disabled; + if ( disabled === false ) { + return; + } + + if ( index === undefined ) { + disabled = false; + } else { + index = this._getIndex( index ); + if ( $.isArray( disabled ) ) { + disabled = $.map( disabled, function( num ) { + return num !== index ? num : null; + }); + } else { + disabled = $.map( this.tabs, function( li, num ) { + return num !== index ? num : null; + }); + } + } + this._setupDisabled( disabled ); + }, + + disable: function( index ) { + var disabled = this.options.disabled; + if ( disabled === true ) { + return; + } + + if ( index === undefined ) { + disabled = true; + } else { + index = this._getIndex( index ); + if ( $.inArray( index, disabled ) !== -1 ) { + return; + } + if ( $.isArray( disabled ) ) { + disabled = $.merge( [ index ], disabled ).sort(); + } else { + disabled = [ index ]; + } + } + this._setupDisabled( disabled ); + }, + + load: function( index, event ) { + index = this._getIndex( index ); + var that = this, + tab = this.tabs.eq( index ), + anchor = tab.find( ".ui-tabs-anchor" ), + panel = this._getPanelForTab( tab ), + eventData = { + tab: tab, + panel: panel + }; + + // not remote + if ( isLocal( anchor[ 0 ] ) ) { + return; + } + + this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); + + // support: jQuery <1.8 + // jQuery <1.8 returns false if the request is canceled in beforeSend, + // but as of 1.8, $.ajax() always returns a jqXHR object. + if ( this.xhr && this.xhr.statusText !== "canceled" ) { + tab.addClass( "ui-tabs-loading" ); + panel.attr( "aria-busy", "true" ); + + this.xhr + .success(function( response ) { + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout(function() { + panel.html( response ); + that._trigger( "load", event, eventData ); + }, 1 ); + }) + .complete(function( jqXHR, status ) { + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout(function() { + if ( status === "abort" ) { + that.panels.stop( false, true ); + } + + tab.removeClass( "ui-tabs-loading" ); + panel.removeAttr( "aria-busy" ); + + if ( jqXHR === that.xhr ) { + delete that.xhr; + } + }, 1 ); + }); + } + }, + + _ajaxSettings: function( anchor, event, eventData ) { + var that = this; + return { + url: anchor.attr( "href" ), + beforeSend: function( jqXHR, settings ) { + return that._trigger( "beforeLoad", event, + $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) ); + } + }; + }, + + _getPanelForTab: function( tab ) { + var id = $( tab ).attr( "aria-controls" ); + return this.element.find( this._sanitizeSelector( "#" + id ) ); + } +}); + +})( jQuery ); + +(function( $ ) { + +var increments = 0; + +function addDescribedBy( elem, id ) { + var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ); + describedby.push( id ); + elem + .data( "ui-tooltip-id", id ) + .attr( "aria-describedby", $.trim( describedby.join( " " ) ) ); +} + +function removeDescribedBy( elem ) { + var id = elem.data( "ui-tooltip-id" ), + describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ), + index = $.inArray( id, describedby ); + if ( index !== -1 ) { + describedby.splice( index, 1 ); + } + + elem.removeData( "ui-tooltip-id" ); + describedby = $.trim( describedby.join( " " ) ); + if ( describedby ) { + elem.attr( "aria-describedby", describedby ); + } else { + elem.removeAttr( "aria-describedby" ); + } +} + +$.widget( "ui.tooltip", { + version: "1.10.2", + options: { + content: function() { + // support: IE<9, Opera in jQuery <1.7 + // .text() can't accept undefined, so coerce to a string + var title = $( this ).attr( "title" ) || ""; + // Escape title, since we're going from an attribute to raw HTML + return $( "<a>" ).text( title ).html(); + }, + hide: true, + // Disabled elements have inconsistent behavior across browsers (#8661) + items: "[title]:not([disabled])", + position: { + my: "left top+15", + at: "left bottom", + collision: "flipfit flip" + }, + show: true, + tooltipClass: null, + track: false, + + // callbacks + close: null, + open: null + }, + + _create: function() { + this._on({ + mouseover: "open", + focusin: "open" + }); + + // IDs of generated tooltips, needed for destroy + this.tooltips = {}; + // IDs of parent tooltips where we removed the title attribute + this.parents = {}; + + if ( this.options.disabled ) { + this._disable(); + } + }, + + _setOption: function( key, value ) { + var that = this; + + if ( key === "disabled" ) { + this[ value ? "_disable" : "_enable" ](); + this.options[ key ] = value; + // disable element style changes + return; + } + + this._super( key, value ); + + if ( key === "content" ) { + $.each( this.tooltips, function( id, element ) { + that._updateContent( element ); + }); + } + }, + + _disable: function() { + var that = this; + + // close open tooltips + $.each( this.tooltips, function( id, element ) { + var event = $.Event( "blur" ); + event.target = event.currentTarget = element[0]; + that.close( event, true ); + }); + + // remove title attributes to prevent native tooltips + this.element.find( this.options.items ).addBack().each(function() { + var element = $( this ); + if ( element.is( "[title]" ) ) { + element + .data( "ui-tooltip-title", element.attr( "title" ) ) + .attr( "title", "" ); + } + }); + }, + + _enable: function() { + // restore title attributes + this.element.find( this.options.items ).addBack().each(function() { + var element = $( this ); + if ( element.data( "ui-tooltip-title" ) ) { + element.attr( "title", element.data( "ui-tooltip-title" ) ); + } + }); + }, + + open: function( event ) { + var that = this, + target = $( event ? event.target : this.element ) + // we need closest here due to mouseover bubbling, + // but always pointing at the same event target + .closest( this.options.items ); + + // No element to show a tooltip for or the tooltip is already open + if ( !target.length || target.data( "ui-tooltip-id" ) ) { + return; + } + + if ( target.attr( "title" ) ) { + target.data( "ui-tooltip-title", target.attr( "title" ) ); + } + + target.data( "ui-tooltip-open", true ); + + // kill parent tooltips, custom or native, for hover + if ( event && event.type === "mouseover" ) { + target.parents().each(function() { + var parent = $( this ), + blurEvent; + if ( parent.data( "ui-tooltip-open" ) ) { + blurEvent = $.Event( "blur" ); + blurEvent.target = blurEvent.currentTarget = this; + that.close( blurEvent, true ); + } + if ( parent.attr( "title" ) ) { + parent.uniqueId(); + that.parents[ this.id ] = { + element: this, + title: parent.attr( "title" ) + }; + parent.attr( "title", "" ); + } + }); + } + + this._updateContent( target, event ); + }, + + _updateContent: function( target, event ) { + var content, + contentOption = this.options.content, + that = this, + eventType = event ? event.type : null; + + if ( typeof contentOption === "string" ) { + return this._open( event, target, contentOption ); + } + + content = contentOption.call( target[0], function( response ) { + // ignore async response if tooltip was closed already + if ( !target.data( "ui-tooltip-open" ) ) { + return; + } + // IE may instantly serve a cached response for ajax requests + // delay this call to _open so the other call to _open runs first + that._delay(function() { + // jQuery creates a special event for focusin when it doesn't + // exist natively. To improve performance, the native event + // object is reused and the type is changed. Therefore, we can't + // rely on the type being correct after the event finished + // bubbling, so we set it back to the previous value. (#8740) + if ( event ) { + event.type = eventType; + } + this._open( event, target, response ); + }); + }); + if ( content ) { + this._open( event, target, content ); + } + }, + + _open: function( event, target, content ) { + var tooltip, events, delayedShow, + positionOption = $.extend( {}, this.options.position ); + + if ( !content ) { + return; + } + + // Content can be updated multiple times. If the tooltip already + // exists, then just update the content and bail. + tooltip = this._find( target ); + if ( tooltip.length ) { + tooltip.find( ".ui-tooltip-content" ).html( content ); + return; + } + + // if we have a title, clear it to prevent the native tooltip + // we have to check first to avoid defining a title if none exists + // (we don't want to cause an element to start matching [title]) + // + // We use removeAttr only for key events, to allow IE to export the correct + // accessible attributes. For mouse events, set to empty string to avoid + // native tooltip showing up (happens only when removing inside mouseover). + if ( target.is( "[title]" ) ) { + if ( event && event.type === "mouseover" ) { + target.attr( "title", "" ); + } else { + target.removeAttr( "title" ); + } + } + + tooltip = this._tooltip( target ); + addDescribedBy( target, tooltip.attr( "id" ) ); + tooltip.find( ".ui-tooltip-content" ).html( content ); + + function position( event ) { + positionOption.of = event; + if ( tooltip.is( ":hidden" ) ) { + return; + } + tooltip.position( positionOption ); + } + if ( this.options.track && event && /^mouse/.test( event.type ) ) { + this._on( this.document, { + mousemove: position + }); + // trigger once to override element-relative positioning + position( event ); + } else { + tooltip.position( $.extend({ + of: target + }, this.options.position ) ); + } + + tooltip.hide(); + + this._show( tooltip, this.options.show ); + // Handle tracking tooltips that are shown with a delay (#8644). As soon + // as the tooltip is visible, position the tooltip using the most recent + // event. + if ( this.options.show && this.options.show.delay ) { + delayedShow = this.delayedShow = setInterval(function() { + if ( tooltip.is( ":visible" ) ) { + position( positionOption.of ); + clearInterval( delayedShow ); + } + }, $.fx.interval ); + } + + this._trigger( "open", event, { tooltip: tooltip } ); + + events = { + keyup: function( event ) { + if ( event.keyCode === $.ui.keyCode.ESCAPE ) { + var fakeEvent = $.Event(event); + fakeEvent.currentTarget = target[0]; + this.close( fakeEvent, true ); + } + }, + remove: function() { + this._removeTooltip( tooltip ); + } + }; + if ( !event || event.type === "mouseover" ) { + events.mouseleave = "close"; + } + if ( !event || event.type === "focusin" ) { + events.focusout = "close"; + } + this._on( true, target, events ); + }, + + close: function( event ) { + var that = this, + target = $( event ? event.currentTarget : this.element ), + tooltip = this._find( target ); + + // disabling closes the tooltip, so we need to track when we're closing + // to avoid an infinite loop in case the tooltip becomes disabled on close + if ( this.closing ) { + return; + } + + // Clear the interval for delayed tracking tooltips + clearInterval( this.delayedShow ); + + // only set title if we had one before (see comment in _open()) + if ( target.data( "ui-tooltip-title" ) ) { + target.attr( "title", target.data( "ui-tooltip-title" ) ); + } + + removeDescribedBy( target ); + + tooltip.stop( true ); + this._hide( tooltip, this.options.hide, function() { + that._removeTooltip( $( this ) ); + }); + + target.removeData( "ui-tooltip-open" ); + this._off( target, "mouseleave focusout keyup" ); + // Remove 'remove' binding only on delegated targets + if ( target[0] !== this.element[0] ) { + this._off( target, "remove" ); + } + this._off( this.document, "mousemove" ); + + if ( event && event.type === "mouseleave" ) { + $.each( this.parents, function( id, parent ) { + $( parent.element ).attr( "title", parent.title ); + delete that.parents[ id ]; + }); + } + + this.closing = true; + this._trigger( "close", event, { tooltip: tooltip } ); + this.closing = false; + }, + + _tooltip: function( element ) { + var id = "ui-tooltip-" + increments++, + tooltip = $( "<div>" ) + .attr({ + id: id, + role: "tooltip" + }) + .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " + + ( this.options.tooltipClass || "" ) ); + $( "<div>" ) + .addClass( "ui-tooltip-content" ) + .appendTo( tooltip ); + tooltip.appendTo( this.document[0].body ); + this.tooltips[ id ] = element; + return tooltip; + }, + + _find: function( target ) { + var id = target.data( "ui-tooltip-id" ); + return id ? $( "#" + id ) : $(); + }, + + _removeTooltip: function( tooltip ) { + tooltip.remove(); + delete this.tooltips[ tooltip.attr( "id" ) ]; + }, + + _destroy: function() { + var that = this; + + // close open tooltips + $.each( this.tooltips, function( id, element ) { + // Delegate to close method to handle common cleanup + var event = $.Event( "blur" ); + event.target = event.currentTarget = element[0]; + that.close( event, true ); + + // Remove immediately; destroying an open tooltip doesn't use the + // hide animation + $( "#" + id ).remove(); + + // Restore the title + if ( element.data( "ui-tooltip-title" ) ) { + element.attr( "title", element.data( "ui-tooltip-title" ) ); + element.removeData( "ui-tooltip-title" ); + } + }); + } +}); + +}( jQuery ) ); diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/3rdparty/jquery-ui/js/jquery-ui.min.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/3rdparty/jquery-ui/js/jquery-ui.min.js new file mode 100644 index 0000000..629f140 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/3rdparty/jquery-ui/js/jquery-ui.min.js @@ -0,0 +1,12 @@ +/*! jQuery UI - v1.10.2 - 2013-03-14 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.effect.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.position.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js +* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ +(function(t,e){function i(e,i){var n,o,a,r=e.nodeName.toLowerCase();return"area"===r?(n=e.parentNode,o=n.name,e.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap=#"+o+"]")[0],!!a&&s(a)):!1):(/input|select|textarea|button|object/.test(r)?!e.disabled:"a"===r?e.href||i:i)&&s(e)}function s(e){return t.expr.filters.visible(e)&&!t(e).parents().addBack().filter(function(){return"hidden"===t.css(this,"visibility")}).length}var n=0,o=/^ui-id-\d+$/;t.ui=t.ui||{},t.extend(t.ui,{version:"1.10.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),t.fn.extend({focus:function(e){return function(i,s){return"number"==typeof i?this.each(function(){var e=this;setTimeout(function(){t(e).focus(),s&&s.call(e)},i)}):e.apply(this,arguments)}}(t.fn.focus),scrollParent:function(){var e;return e=t.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(t.css(this,"position"))&&/(auto|scroll)/.test(t.css(this,"overflow")+t.css(this,"overflow-y")+t.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(t.css(this,"overflow")+t.css(this,"overflow-y")+t.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!e.length?t(document):e},zIndex:function(i){if(i!==e)return this.css("zIndex",i);if(this.length)for(var s,n,o=t(this[0]);o.length&&o[0]!==document;){if(s=o.css("position"),("absolute"===s||"relative"===s||"fixed"===s)&&(n=parseInt(o.css("zIndex"),10),!isNaN(n)&&0!==n))return n;o=o.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){o.test(this.id)&&t(this).removeAttr("id")})}}),t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])},focusable:function(e){return i(e,!isNaN(t.attr(e,"tabindex")))},tabbable:function(e){var s=t.attr(e,"tabindex"),n=isNaN(s);return(n||s>=0)&&i(e,!n)}}),t("<a>").outerWidth(1).jquery||t.each(["Width","Height"],function(i,s){function n(e,i,s,n){return t.each(o,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),n&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var o="Width"===s?["Left","Right"]:["Top","Bottom"],a=s.toLowerCase(),r={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+s]=function(i){return i===e?r["inner"+s].call(this):this.each(function(){t(this).css(a,n(this,i)+"px")})},t.fn["outer"+s]=function(e,i){return"number"!=typeof e?r["outer"+s].call(this,e):this.each(function(){t(this).css(a,n(this,e,!0,i)+"px")})}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(t.fn.removeData=function(e){return function(i){return arguments.length?e.call(this,t.camelCase(i)):e.call(this)}}(t.fn.removeData)),t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),t.support.selectstart="onselectstart"in document.createElement("div"),t.fn.extend({disableSelection:function(){return this.bind((t.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(t){t.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),t.extend(t.ui,{plugin:{add:function(e,i,s){var n,o=t.ui[e].prototype;for(n in s)o.plugins[n]=o.plugins[n]||[],o.plugins[n].push([i,s[n]])},call:function(t,e,i){var s,n=t.plugins[e];if(n&&t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType)for(s=0;n.length>s;s++)t.options[n[s][0]]&&n[s][1].apply(t.element,i)}},hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return e[s]>0?!0:(e[s]=1,n=e[s]>0,e[s]=0,n)}})})(jQuery),function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),s===e)return o[i]===e?null:o[i];o[i]=s}else{if(s===e)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})}(jQuery),function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget("ui.mouse",{version:"1.10.2",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).bind("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,o="string"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!o&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+".preventClickEvent")&&t.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})}(jQuery),function(t){t.widget("ui.draggable",t.ui.mouse,{version:"1.10.2",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"!==this.options.helper||/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(t(i.iframeFix===!0?"iframe":i.iframeFix).each(function(){t("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(t(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),i.containment&&this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_mouseDrag:function(e,i){if(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i,s=this,n=!1,o=!1;for(t.ui.ddmanager&&!this.options.dropBehaviour&&(o=t.ui.ddmanager.drop(this,e)),this.dropped&&(o=this.dropped,this.dropped=!1),i=this.element[0];i&&(i=i.parentNode);)i===document&&(n=!0);return n||"original"!==this.options.helper?("invalid"===this.options.revert&&!o||"valid"===this.options.revert&&o||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,o)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){s._trigger("stop",e)!==!1&&s._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1):!1},_mouseUp:function(e){return t("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return s.parents("body").length||s.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s[0]===this.element[0]||/(fixed|absolute)/.test(s.css("position"))||s.css("position","absolute"),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.element.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;if("parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=["document"===n.containment?0:t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,"document"===n.containment?0:t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,("document"===n.containment?0:t(window).scrollLeft())+t("document"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,("document"===n.containment?0:t(window).scrollTop())+(t("document"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||n.containment.constructor===Array)n.containment.constructor===Array&&(this.containment=n.containment);else{if(i=t(n.containment),s=i[0],!s)return;e="hidden"!==t(s).css("overflow"),this.containment=[(parseInt(t(s).css("borderLeftWidth"),10)||0)+(parseInt(t(s).css("paddingLeft"),10)||0),(parseInt(t(s).css("borderTopWidth"),10)||0)+(parseInt(t(s).css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(t(s).css("borderRightWidth"),10)||0)-(parseInt(t(s).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(t(s).css("borderBottomWidth"),10)||0)-(parseInt(t(s).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=i}},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n,o,a=this.options,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName),l=e.pageX,c=e.pageY;return this.originalPosition&&(this.containment&&(this.relative_container?(s=this.relative_container.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.left<i[0]&&(l=i[0]+this.offset.click.left),e.pageY-this.offset.click.top<i[1]&&(c=i[1]+this.offset.click.top),e.pageX-this.offset.click.left>i[2]&&(l=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(c=i[3]+this.offset.click.top)),a.grid&&(n=a.grid[1]?this.originalPageY+Math.round((c-this.originalPageY)/a.grid[1])*a.grid[1]:this.originalPageY,c=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-a.grid[1]:n+a.grid[1]:n,o=a.grid[0]?this.originalPageX+Math.round((l-this.originalPageX)/a.grid[0])*a.grid[0]:this.originalPageX,l=i?o-this.offset.click.left>=i[0]||o-this.offset.click.left>i[2]?o:o-this.offset.click.left>=i[0]?o-a.grid[0]:o+a.grid[0]:o)),{top:c-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:l-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s]),"drag"===e&&(this.positionAbs=this._convertPositionTo("absolute")),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i){var s=t(this).data("ui-draggable"),n=s.options,o=t.extend({},i,{item:s.element});s.sortables=[],t(n.connectToSortable).each(function(){var i=t.data(this,"ui-sortable");i&&!i.options.disabled&&(s.sortables.push({instance:i,shouldRevert:i.options.revert}),i.refreshPositions(),i._trigger("activate",e,o))})},stop:function(e,i){var s=t(this).data("ui-draggable"),n=t.extend({},i,{item:s.element});t.each(s.sortables,function(){this.instance.isOver?(this.instance.isOver=0,s.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=this.shouldRevert),this.instance._mouseStop(e),this.instance.options.helper=this.instance.options._helper,"original"===s.options.helper&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",e,n))})},drag:function(e,i){var s=t(this).data("ui-draggable"),n=this;t.each(s.sortables,function(){var o=!1,a=this;this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(o=!0,t.each(s.sortables,function(){return this.instance.positionAbs=s.positionAbs,this.instance.helperProportions=s.helperProportions,this.instance.offset.click=s.offset.click,this!==a&&this.instance._intersectsWith(this.instance.containerCache)&&t.contains(a.instance.element[0],this.instance.element[0])&&(o=!1),o})),o?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=t(n).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return i.helper[0]},e.target=this.instance.currentItem[0],this.instance._mouseCapture(e,!0),this.instance._mouseStart(e,!0,!0),this.instance.offset.click.top=s.offset.click.top,this.instance.offset.click.left=s.offset.click.left,this.instance.offset.parent.left-=s.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=s.offset.parent.top-this.instance.offset.parent.top,s._trigger("toSortable",e),s.dropped=this.instance.element,s.currentItem=s.element,this.instance.fromOutside=s),this.instance.currentItem&&this.instance._mouseDrag(e)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",e,this.instance._uiHash(this.instance)),this.instance._mouseStop(e,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),s._trigger("fromSortable",e),s.dropped=!1)})}}),t.ui.plugin.add("draggable","cursor",{start:function(){var e=t("body"),i=t(this).data("ui-draggable").options;e.css("cursor")&&(i._cursor=e.css("cursor")),e.css("cursor",i.cursor)},stop:function(){var e=t(this).data("ui-draggable").options;e._cursor&&t("body").css("cursor",e._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("opacity")&&(n._opacity=s.css("opacity")),s.css("opacity",n.opacity)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._opacity&&t(i.helper).css("opacity",s._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(){var e=t(this).data("ui-draggable");e.scrollParent[0]!==document&&"HTML"!==e.scrollParent[0].tagName&&(e.overflowOffset=e.scrollParent.offset())},drag:function(e){var i=t(this).data("ui-draggable"),s=i.options,n=!1;i.scrollParent[0]!==document&&"HTML"!==i.scrollParent[0].tagName?(s.axis&&"x"===s.axis||(i.overflowOffset.top+i.scrollParent[0].offsetHeight-e.pageY<s.scrollSensitivity?i.scrollParent[0].scrollTop=n=i.scrollParent[0].scrollTop+s.scrollSpeed:e.pageY-i.overflowOffset.top<s.scrollSensitivity&&(i.scrollParent[0].scrollTop=n=i.scrollParent[0].scrollTop-s.scrollSpeed)),s.axis&&"y"===s.axis||(i.overflowOffset.left+i.scrollParent[0].offsetWidth-e.pageX<s.scrollSensitivity?i.scrollParent[0].scrollLeft=n=i.scrollParent[0].scrollLeft+s.scrollSpeed:e.pageX-i.overflowOffset.left<s.scrollSensitivity&&(i.scrollParent[0].scrollLeft=n=i.scrollParent[0].scrollLeft-s.scrollSpeed))):(s.axis&&"x"===s.axis||(e.pageY-t(document).scrollTop()<s.scrollSensitivity?n=t(document).scrollTop(t(document).scrollTop()-s.scrollSpeed):t(window).height()-(e.pageY-t(document).scrollTop())<s.scrollSensitivity&&(n=t(document).scrollTop(t(document).scrollTop()+s.scrollSpeed))),s.axis&&"y"===s.axis||(e.pageX-t(document).scrollLeft()<s.scrollSensitivity?n=t(document).scrollLeft(t(document).scrollLeft()-s.scrollSpeed):t(window).width()-(e.pageX-t(document).scrollLeft())<s.scrollSensitivity&&(n=t(document).scrollLeft(t(document).scrollLeft()+s.scrollSpeed)))),n!==!1&&t.ui.ddmanager&&!s.dropBehaviour&&t.ui.ddmanager.prepareOffsets(i,e)}}),t.ui.plugin.add("draggable","snap",{start:function(){var e=t(this).data("ui-draggable"),i=e.options;e.snapElements=[],t(i.snap.constructor!==String?i.snap.items||":data(ui-draggable)":i.snap).each(function(){var i=t(this),s=i.offset();this!==e.element[0]&&e.snapElements.push({item:this,width:i.outerWidth(),height:i.outerHeight(),top:s.top,left:s.left})})},drag:function(e,i){var s,n,o,a,r,h,l,c,u,d,p=t(this).data("ui-draggable"),f=p.options,g=f.snapTolerance,m=i.offset.left,v=m+p.helperProportions.width,_=i.offset.top,b=_+p.helperProportions.height;for(u=p.snapElements.length-1;u>=0;u--)r=p.snapElements[u].left,h=r+p.snapElements[u].width,l=p.snapElements[u].top,c=l+p.snapElements[u].height,m>r-g&&h+g>m&&_>l-g&&c+g>_||m>r-g&&h+g>m&&b>l-g&&c+g>b||v>r-g&&h+g>v&&_>l-g&&c+g>_||v>r-g&&h+g>v&&b>l-g&&c+g>b?("inner"!==f.snapMode&&(s=g>=Math.abs(l-b),n=g>=Math.abs(c-_),o=g>=Math.abs(r-v),a=g>=Math.abs(h-m),s&&(i.position.top=p._convertPositionTo("relative",{top:l-p.helperProportions.height,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c,left:0}).top-p.margins.top),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r-p.helperProportions.width}).left-p.margins.left),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:h}).left-p.margins.left)),d=s||n||o||a,"outer"!==f.snapMode&&(s=g>=Math.abs(l-_),n=g>=Math.abs(c-b),o=g>=Math.abs(r-m),a=g>=Math.abs(h-v),s&&(i.position.top=p._convertPositionTo("relative",{top:l,left:0}).top-p.margins.top),n&&(i.position.top=p._convertPositionTo("relative",{top:c-p.helperProportions.height,left:0}).top-p.margins.top),o&&(i.position.left=p._convertPositionTo("relative",{top:0,left:r}).left-p.margins.left),a&&(i.position.left=p._convertPositionTo("relative",{top:0,left:h-p.helperProportions.width}).left-p.margins.left)),!p.snapElements[u].snapping&&(s||n||o||a||d)&&p.options.snap.snap&&p.options.snap.snap.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=s||n||o||a||d):(p.snapElements[u].snapping&&p.options.snap.release&&p.options.snap.release.call(p.element,e,t.extend(p._uiHash(),{snapItem:p.snapElements[u].item})),p.snapElements[u].snapping=!1)}}),t.ui.plugin.add("draggable","stack",{start:function(){var e,i=this.data("ui-draggable").options,s=t.makeArray(t(i.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});s.length&&(e=parseInt(t(s[0]).css("zIndex"),10)||0,t(s).each(function(i){t(this).css("zIndex",e+i)}),this.css("zIndex",e+s.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i){var s=t(i.helper),n=t(this).data("ui-draggable").options;s.css("zIndex")&&(n._zIndex=s.css("zIndex")),s.css("zIndex",n.zIndex)},stop:function(e,i){var s=t(this).data("ui-draggable").options;s._zIndex&&t(i.helper).css("zIndex",s._zIndex)}})}(jQuery),function(t){function e(t,e,i){return t>e&&e+i>t}t.widget("ui.droppable",{version:"1.10.2",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e=this.options,i=e.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(i)?i:function(t){return t.is(i)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},t.ui.ddmanager.droppables[e.scope]=t.ui.ddmanager.droppables[e.scope]||[],t.ui.ddmanager.droppables[e.scope].push(this),e.addClasses&&this.element.addClass("ui-droppable") +},_destroy:function(){for(var e=0,i=t.ui.ddmanager.droppables[this.options.scope];i.length>e;e++)i[e]===this&&i.splice(e,1);this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(e,i){"accept"===e&&(this.accept=t.isFunction(i)?i:function(t){return t.is(i)}),t.Widget.prototype._setOption.apply(this,arguments)},_activate:function(e){var i=t.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var e=t.data(this,"ui-droppable");return e.options.greedy&&!e.options.disabled&&e.options.scope===s.options.scope&&e.accept.call(e.element[0],s.currentItem||s.element)&&t.ui.intersect(s,t.extend(e,{offset:e.element.offset()}),e.options.tolerance)?(n=!0,!1):undefined}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}}}),t.ui.intersect=function(t,i,s){if(!i.offset)return!1;var n,o,a=(t.positionAbs||t.position.absolute).left,r=a+t.helperProportions.width,h=(t.positionAbs||t.position.absolute).top,l=h+t.helperProportions.height,c=i.offset.left,u=c+i.proportions.width,d=i.offset.top,p=d+i.proportions.height;switch(s){case"fit":return a>=c&&u>=r&&h>=d&&p>=l;case"intersect":return a+t.helperProportions.width/2>c&&u>r-t.helperProportions.width/2&&h+t.helperProportions.height/2>d&&p>l-t.helperProportions.height/2;case"pointer":return n=(t.positionAbs||t.position.absolute).left+(t.clickOffset||t.offset.click).left,o=(t.positionAbs||t.position.absolute).top+(t.clickOffset||t.offset.click).top,e(o,d,i.proportions.height)&&e(n,c,i.proportions.width);case"touch":return(h>=d&&p>=h||l>=d&&p>=l||d>h&&l>p)&&(a>=c&&u>=a||r>=c&&u>=r||c>a&&r>u);default:return!1}},t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,o=t.ui.ddmanager.droppables[e.options.scope]||[],a=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;o.length>s;s++)if(!(o[s].options.disabled||e&&!o[s].accept.call(o[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===o[s].element[0]){o[s].proportions.height=0;continue t}o[s].visible="none"!==o[s].element.css("display"),o[s].visible&&("mousedown"===a&&o[s]._activate.call(o[s],i),o[s].offset=o[s].element.offset(),o[s].proportions={width:o[s].element[0].offsetWidth,height:o[s].element[0].offsetHeight})}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&t.ui.intersect(e,this,this.options.tolerance)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").bind("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,o,a=t.ui.intersect(e,this,this.options.tolerance),r=!a&&this.isover?"isout":a&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,o=this.element.parents(":data(ui-droppable)").filter(function(){return t.data(this,"ui-droppable").options.scope===n}),o.length&&(s=t.data(o[0],"ui-droppable"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").unbind("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}}}(jQuery),function(t){function e(t){return parseInt(t,10)||0}function i(t){return!isNaN(parseInt(t,10))}t.widget("ui.resizable",t.ui.mouse,{version:"1.10.2",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_create:function(){var e,i,s,n,o,a=this,r=this.options;if(this.element.addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(t("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),e=this.handles.split(","),this.handles={},i=0;e.length>i;i++)s=t.trim(e[i]),o="ui-resizable-"+s,n=t("<div class='ui-resizable-handle "+o+"'></div>"),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(e){var i,s,n,o;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String&&(this.handles[i]=t(this.handles[i],this.element).show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(s=t(this.handles[i],this.element),o=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,o),this._proportionallyResize()),t(this.handles[i]).length},this._renderAxis(this.element),this._handles=t(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){a.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),a.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),t(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(t(this).removeClass("ui-resizable-autohide"),a._handles.show())}).mouseleave(function(){r.disabled||a.resizing||(t(this).addClass("ui-resizable-autohide"),a._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(i){var s,n,o,a=this.options,r=this.element.position(),h=this.element;return this.resizing=!0,/absolute/.test(h.css("position"))?h.css({position:"absolute",top:h.css("top"),left:h.css("left")}):h.is(".ui-draggable")&&h.css({position:"absolute",top:r.top,left:r.left}),this._renderProxy(),s=e(this.helper.css("left")),n=e(this.helper.css("top")),a.containment&&(s+=t(a.containment).scrollLeft()||0,n+=t(a.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:s,top:n},this.size=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalSize=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalPosition={left:s,top:n},this.sizeDiff={width:h.outerWidth()-h.width(),height:h.outerHeight()-h.height()},this.originalMousePosition={left:i.pageX,top:i.pageY},this.aspectRatio="number"==typeof a.aspectRatio?a.aspectRatio:this.originalSize.width/this.originalSize.height||1,o=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===o?this.axis+"-resize":o),h.addClass("ui-resizable-resizing"),this._propagate("start",i),!0},_mouseDrag:function(e){var i,s=this.helper,n={},o=this.originalMousePosition,a=this.axis,r=this.position.top,h=this.position.left,l=this.size.width,c=this.size.height,u=e.pageX-o.left||0,d=e.pageY-o.top||0,p=this._change[a];return p?(i=p.apply(this,[e,u,d]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),this.position.top!==r&&(n.top=this.position.top+"px"),this.position.left!==h&&(n.left=this.position.left+"px"),this.size.width!==l&&(n.width=this.size.width+"px"),this.size.height!==c&&(n.height=this.size.height+"px"),s.css(n),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(n)||this._trigger("resize",e,this.ui()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,o,a,r,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&t.ui.hasScroll(i[0],"left")?0:c.sizeDiff.height,o=s?0:c.sizeDiff.width,a={width:c.helper.width()-o,height:c.helper.height()-n},r=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null,h=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(a,{top:h,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(t){var e,s,n,o,a,r=this.options;a={minWidth:i(r.minWidth)?r.minWidth:0,maxWidth:i(r.maxWidth)?r.maxWidth:1/0,minHeight:i(r.minHeight)?r.minHeight:0,maxHeight:i(r.maxHeight)?r.maxHeight:1/0},(this._aspectRatio||t)&&(e=a.minHeight*this.aspectRatio,n=a.minWidth/this.aspectRatio,s=a.maxHeight*this.aspectRatio,o=a.maxWidth/this.aspectRatio,e>a.minWidth&&(a.minWidth=e),n>a.minHeight&&(a.minHeight=n),a.maxWidth>s&&(a.maxWidth=s),a.maxHeight>o&&(a.maxHeight=o)),this._vBoundaries=a},_updateCache:function(t){this.offset=this.helper.offset(),i(t.left)&&(this.position.left=t.left),i(t.top)&&(this.position.top=t.top),i(t.height)&&(this.size.height=t.height),i(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,s=this.size,n=this.axis;return i(t.height)?t.width=t.height*this.aspectRatio:i(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===n&&(t.left=e.left+(s.width-t.width),t.top=null),"nw"===n&&(t.top=e.top+(s.height-t.height),t.left=e.left+(s.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,s=this.axis,n=i(t.width)&&e.maxWidth&&e.maxWidth<t.width,o=i(t.height)&&e.maxHeight&&e.maxHeight<t.height,a=i(t.width)&&e.minWidth&&e.minWidth>t.width,r=i(t.height)&&e.minHeight&&e.minHeight>t.height,h=this.originalPosition.left+this.originalSize.width,l=this.position.top+this.size.height,c=/sw|nw|w/.test(s),u=/nw|ne|n/.test(s);return a&&(t.width=e.minWidth),r&&(t.height=e.minHeight),n&&(t.width=e.maxWidth),o&&(t.height=e.maxHeight),a&&c&&(t.left=h-e.minWidth),n&&c&&(t.left=h-e.maxWidth),r&&u&&(t.top=l-e.minHeight),o&&u&&(t.top=l-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_proportionallyResize:function(){if(this._proportionallyResizeElements.length){var t,e,i,s,n,o=this.helper||this.element;for(t=0;this._proportionallyResizeElements.length>t;t++){if(n=this._proportionallyResizeElements[t],!this.borderDif)for(this.borderDif=[],i=[n.css("borderTopWidth"),n.css("borderRightWidth"),n.css("borderBottomWidth"),n.css("borderLeftWidth")],s=[n.css("paddingTop"),n.css("paddingRight"),n.css("paddingBottom"),n.css("paddingLeft")],e=0;i.length>e;e++)this.borderDif[e]=(parseInt(i[e],10)||0)+(parseInt(s[e],10)||0);n.css({height:o.height()-this.borderDif[0]-this.borderDif[2]||0,width:o.width()-this.borderDif[1]-this.borderDif[3]||0})}}},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("<div style='overflow:hidden;'></div>"),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).data("ui-resizable"),s=i.options,n=i._proportionallyResizeElements,o=n.length&&/textarea/i.test(n[0].nodeName),a=o&&t.ui.hasScroll(n[0],"left")?0:i.sizeDiff.height,r=o?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-a},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,c=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var i,s,n,o,a,r,h,l=t(this).data("ui-resizable"),c=l.options,u=l.element,d=c.containment,p=d instanceof t?d.get(0):/parent/.test(d)?u.parent().get(0):d;p&&(l.containerElement=t(p),/document/.test(d)||d===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(i=t(p),s=[],t(["Top","Right","Left","Bottom"]).each(function(t,n){s[t]=e(i.css("padding"+n))}),l.containerOffset=i.offset(),l.containerPosition=i.position(),l.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},n=l.containerOffset,o=l.containerSize.height,a=l.containerSize.width,r=t.ui.hasScroll(p,"left")?p.scrollWidth:a,h=t.ui.hasScroll(p)?p.scrollHeight:o,l.parentData={element:p,left:n.left,top:n.top,width:r,height:h}))},resize:function(e){var i,s,n,o,a=t(this).data("ui-resizable"),r=a.options,h=a.containerOffset,l=a.position,c=a._aspectRatio||e.shiftKey,u={top:0,left:0},d=a.containerElement;d[0]!==document&&/static/.test(d.css("position"))&&(u=h),l.left<(a._helper?h.left:0)&&(a.size.width=a.size.width+(a._helper?a.position.left-h.left:a.position.left-u.left),c&&(a.size.height=a.size.width/a.aspectRatio),a.position.left=r.helper?h.left:0),l.top<(a._helper?h.top:0)&&(a.size.height=a.size.height+(a._helper?a.position.top-h.top:a.position.top),c&&(a.size.width=a.size.height*a.aspectRatio),a.position.top=a._helper?h.top:0),a.offset.left=a.parentData.left+a.position.left,a.offset.top=a.parentData.top+a.position.top,i=Math.abs((a._helper?a.offset.left-u.left:a.offset.left-u.left)+a.sizeDiff.width),s=Math.abs((a._helper?a.offset.top-u.top:a.offset.top-h.top)+a.sizeDiff.height),n=a.containerElement.get(0)===a.element.parent().get(0),o=/relative|absolute/.test(a.containerElement.css("position")),n&&o&&(i-=a.parentData.left),i+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-i,c&&(a.size.height=a.size.width/a.aspectRatio)),s+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-s,c&&(a.size.width=a.size.height*a.aspectRatio))},stop:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.containerOffset,n=e.containerPosition,o=e.containerElement,a=t(e.helper),r=a.offset(),h=a.outerWidth()-e.sizeDiff.width,l=a.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).data("ui-resizable"),i=e.options,s=function(e){t(e).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseInt(e.width(),10),height:parseInt(e.height(),10),left:parseInt(e.css("left"),10),top:parseInt(e.css("top"),10)})})};"object"!=typeof i.alsoResize||i.alsoResize.parentNode?s(i.alsoResize):i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):t.each(i.alsoResize,function(t){s(t)})},resize:function(e,i){var s=t(this).data("ui-resizable"),n=s.options,o=s.originalSize,a=s.originalPosition,r={height:s.size.height-o.height||0,width:s.size.width-o.width||0,top:s.position.top-a.top||0,left:s.position.left-a.left||0},h=function(e,s){t(e).each(function(){var e=t(this),n=t(this).data("ui-resizable-alsoresize"),o={},a=s&&s.length?s:e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(a,function(t,e){var i=(n[e]||0)+(r[e]||0);i&&i>=0&&(o[e]=i||null)}),e.css(o)})};"object"!=typeof n.alsoResize||n.alsoResize.nodeType?h(n.alsoResize):t.each(n.alsoResize,function(t,e){h(t,e)})},stop:function(){t(this).removeData("resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).data("ui-resizable");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).data("ui-resizable");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e=t(this).data("ui-resizable"),i=e.options,s=e.size,n=e.originalSize,o=e.originalPosition,a=e.axis,r="number"==typeof i.grid?[i.grid,i.grid]:i.grid,h=r[0]||1,l=r[1]||1,c=Math.round((s.width-n.width)/h)*h,u=Math.round((s.height-n.height)/l)*l,d=n.width+c,p=n.height+u,f=i.maxWidth&&d>i.maxWidth,g=i.maxHeight&&p>i.maxHeight,m=i.minWidth&&i.minWidth>d,v=i.minHeight&&i.minHeight>p;i.grid=r,m&&(d+=h),v&&(p+=l),f&&(d-=h),g&&(p-=l),/^(se|s|e)$/.test(a)?(e.size.width=d,e.size.height=p):/^(ne)$/.test(a)?(e.size.width=d,e.size.height=p,e.position.top=o.top-u):/^(sw)$/.test(a)?(e.size.width=d,e.size.height=p,e.position.left=o.left-c):(e.size.width=d,e.size.height=p,e.position.top=o.top-u,e.position.left=o.left-c)}})}(jQuery),function(t){t.widget("ui.selectable",t.ui.mouse,{version:"1.10.2",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e,i=this;this.element.addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e=t(i.options.filter,i.element[0]),e.addClass("ui-selectee"),e.each(function(){var e=t(this),i=e.offset();t.data(this,"selectable-item",{element:this,$element:e,left:i.left,top:i.top,right:i.left+e.outerWidth(),bottom:i.top+e.outerHeight(),startselected:!1,selected:e.hasClass("ui-selected"),selecting:e.hasClass("ui-selecting"),unselecting:e.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=e.addClass("ui-selectee"),this._mouseInit(),this.helper=t("<div class='ui-selectable-helper'></div>")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(s.$element.removeClass("ui-selected"),s.selected=!1,s.$element.addClass("ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,n=t.data(this,"selectable-item");return n?(s=!e.metaKey&&!e.ctrlKey||!n.$element.hasClass("ui-selected"),n.$element.removeClass(s?"ui-unselecting":"ui-selected").addClass(s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",e,{selecting:n.element}):i._trigger("unselecting",e,{unselecting:n.element}),!1):undefined}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,o=this.opos[0],a=this.opos[1],r=e.pageX,h=e.pageY;return o>r&&(i=r,r=o,o=i),a>h&&(i=h,h=a,a=i),this.helper.css({left:o,top:a,width:r-o,height:h-a}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),l=!1;i&&i.element!==s.element[0]&&("touch"===n.tolerance?l=!(i.left>r||o>i.right||i.top>h||a>i.bottom):"fit"===n.tolerance&&(l=i.left>o&&r>i.right&&i.top>a&&h>i.bottom),l?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");s.$element.removeClass("ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");s.$element.removeClass("ui-selecting").addClass("ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}})}(jQuery),function(t){function e(t,e,i){return t>e&&e+i>t}function i(t){return/left|right/.test(t.css("float"))||/inline|table-cell/.test(t.css("display"))}t.widget("ui.sortable",t.ui.mouse,{version:"1.10.2",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var t=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?"x"===t.axis||i(this.items[0].item):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var t=this.items.length-1;t>=0;t--)this.items[t].item.removeData(this.widgetName+"-item");return this},_setOption:function(e,i){"disabled"===e?(this.options[e]=i,this.widget().toggleClass("ui-sortable-disabled",!!i)):t.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(e,i){var s=null,n=!1,o=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(e),t(e.target).parents().each(function(){return t.data(this,o.widgetName+"-item")===o?(s=t(this),!1):undefined}),t.data(e.target,o.widgetName+"-item")===o&&(s=t(e.target)),s?!this.options.handle||i||(t(this.options.handle,s).find("*").addBack().each(function(){this===e.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(e,i,s){var n,o,a=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(e),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},t.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(e),this.originalPageX=e.pageX,this.originalPageY=e.pageY,a.cursorAt&&this._adjustOffsetFromHelper(a.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),a.containment&&this._setContainment(),a.cursor&&"auto"!==a.cursor&&(o=this.document.find("body"),this.storedCursor=o.css("cursor"),o.css("cursor",a.cursor),this.storedStylesheet=t("<style>*{ cursor: "+a.cursor+" !important; }</style>").appendTo(o)),a.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",a.opacity)),a.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",a.zIndex)),this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",e,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",e,this._uiHash(this));return t.ui.ddmanager&&(t.ui.ddmanager.current=this),t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(e),!0},_mouseDrag:function(e){var i,s,n,o,a=this.options,r=!1;for(this.position=this._generatePosition(e),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==document&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-e.pageY<a.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+a.scrollSpeed:e.pageY-this.overflowOffset.top<a.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-a.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-e.pageX<a.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+a.scrollSpeed:e.pageX-this.overflowOffset.left<a.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-a.scrollSpeed)):(e.pageY-t(document).scrollTop()<a.scrollSensitivity?r=t(document).scrollTop(t(document).scrollTop()-a.scrollSpeed):t(window).height()-(e.pageY-t(document).scrollTop())<a.scrollSensitivity&&(r=t(document).scrollTop(t(document).scrollTop()+a.scrollSpeed)),e.pageX-t(document).scrollLeft()<a.scrollSensitivity?r=t(document).scrollLeft(t(document).scrollLeft()-a.scrollSpeed):t(window).width()-(e.pageX-t(document).scrollLeft())<a.scrollSensitivity&&(r=t(document).scrollLeft(t(document).scrollLeft()+a.scrollSpeed))),r!==!1&&t.ui.ddmanager&&!a.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],o=this._intersectsWithPointer(s),o&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===o?"next":"prev"]()[0]!==n&&!t.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!t.contains(this.element[0],n):!0)){if(this.direction=1===o?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break; +this._rearrange(e,s),this._trigger("change",e,this._uiHash());break}return this._contactContainers(e),t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),this._trigger("sort",e,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(e,i){if(e){if(t.ui.ddmanager&&!this.options.dropBehaviour&&t.ui.ddmanager.drop(this,e),this.options.revert){var s=this,n=this.placeholder.offset(),o=this.options.axis,a={};o&&"x"!==o||(a.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)),o&&"y"!==o||(a.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,t(this.helper).animate(a,parseInt(this.options.revert,10)||500,function(){s._clear(e)})}else this._clear(e,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("deactivate",null,this._uiHash(this)),this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",null,this._uiHash(this)),this.containers[e].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),t.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?t(this.domPosition.prev).after(this.currentItem):t(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},t(i).each(function(){var i=(t(e.item||this).attr(e.attribute||"id")||"").match(e.expression||/(.+)[\-=_](.+)/);i&&s.push((e.key||i[1]+"[]")+"="+(e.key&&e.expression?i[1]:i[2]))}),!s.length&&e.key&&s.push(e.key+"="),s.join("&")},toArray:function(e){var i=this._getItemsAsjQuery(e&&e.connected),s=[];return e=e||{},i.each(function(){s.push(t(e.item||this).attr(e.attribute||"id")||"")}),s},_intersectsWith:function(t){var e=this.positionAbs.left,i=e+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,o=t.left,a=o+t.width,r=t.top,h=r+t.height,l=this.offset.click.top,c=this.offset.click.left,u=s+l>r&&h>s+l&&e+c>o&&a>e+c;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>t[this.floating?"width":"height"]?u:e+this.helperProportions.width/2>o&&a>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(t){var i="x"===this.options.axis||e(this.positionAbs.top+this.offset.click.top,t.top,t.height),s="y"===this.options.axis||e(this.positionAbs.left+this.offset.click.left,t.left,t.width),n=i&&s,o=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return n?this.floating?a&&"right"===a||"down"===o?2:1:o&&("down"===o?2:1):!1},_intersectsWithSides:function(t){var i=e(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),s=e(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),n=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return this.floating&&o?"right"===o&&s||"left"===o&&!s:n&&("down"===n&&i||"up"===n&&!i)},_getDragVerticalDirection:function(){var t=this.positionAbs.top-this.lastPositionAbs.top;return 0!==t&&(t>0?"down":"up")},_getDragHorizontalDirection:function(){var t=this.positionAbs.left-this.lastPositionAbs.left;return 0!==t&&(t>0?"right":"left")},refresh:function(t){return this._refreshItems(t),this.refreshPositions(),this},_connectWith:function(){var t=this.options;return t.connectWith.constructor===String?[t.connectWith]:t.connectWith},_getItemsAsjQuery:function(e){var i,s,n,o,a=[],r=[],h=this._connectWith();if(h&&e)for(i=h.length-1;i>=0;i--)for(n=t(h[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&r.push([t.isFunction(o.options.items)?o.options.items.call(o.element):t(o.options.items,o.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),o]);for(r.push([t.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):t(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),i=r.length-1;i>=0;i--)r[i][0].each(function(){a.push(this)});return t(a)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=t.grep(this.items,function(t){for(var i=0;e.length>i;i++)if(e[i]===t.item[0])return!1;return!0})},_refreshItems:function(e){this.items=[],this.containers=[this];var i,s,n,o,a,r,h,l,c=this.items,u=[[t.isFunction(this.options.items)?this.options.items.call(this.element[0],e,{item:this.currentItem}):t(this.options.items,this.element),this]],d=this._connectWith();if(d&&this.ready)for(i=d.length-1;i>=0;i--)for(n=t(d[i]),s=n.length-1;s>=0;s--)o=t.data(n[s],this.widgetFullName),o&&o!==this&&!o.options.disabled&&(u.push([t.isFunction(o.options.items)?o.options.items.call(o.element[0],e,{item:this.currentItem}):t(o.options.items,o.element),o]),this.containers.push(o));for(i=u.length-1;i>=0;i--)for(a=u[i][1],r=u[i][0],s=0,l=r.length;l>s;s++)h=t(r[s]),h.data(this.widgetName+"-item",a),c.push({item:h,instance:a,width:0,height:0,left:0,top:0})},refreshPositions:function(e){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,o;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?t(this.options.toleranceElement,s.item):s.item,e||(s.width=n.outerWidth(),s.height=n.outerHeight()),o=n.offset(),s.left=o.left,s.top=o.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)o=this.containers[i].element.offset(),this.containers[i].containerCache.left=o.left,this.containers[i].containerCache.top=o.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(e){e=e||this;var i,s=e.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=e.currentItem[0].nodeName.toLowerCase(),n=t(e.document[0].createElement(s)).addClass(i||e.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tr"===s?n.append("<td colspan='99'> </td>"):"img"===s&&n.attr("src",e.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(t,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(e.currentItem.innerHeight()-parseInt(e.currentItem.css("paddingTop")||0,10)-parseInt(e.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(e.currentItem.innerWidth()-parseInt(e.currentItem.css("paddingLeft")||0,10)-parseInt(e.currentItem.css("paddingRight")||0,10)))}}),e.placeholder=t(s.placeholder.element.call(e.element,e.currentItem)),e.currentItem.after(e.placeholder),s.placeholder.update(e,e.placeholder)},_contactContainers:function(s){var n,o,a,r,h,l,c,u,d,p,f=null,g=null;for(n=this.containers.length-1;n>=0;n--)if(!t.contains(this.currentItem[0],this.containers[n].element[0]))if(this._intersectsWith(this.containers[n].containerCache)){if(f&&t.contains(this.containers[n].element[0],f.element[0]))continue;f=this.containers[n],g=n}else this.containers[n].containerCache.over&&(this.containers[n]._trigger("out",s,this._uiHash(this)),this.containers[n].containerCache.over=0);if(f)if(1===this.containers.length)this.containers[g].containerCache.over||(this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1);else{for(a=1e4,r=null,p=f.floating||i(this.currentItem),h=p?"left":"top",l=p?"width":"height",c=this.positionAbs[h]+this.offset.click[h],o=this.items.length-1;o>=0;o--)t.contains(this.containers[g].element[0],this.items[o].item[0])&&this.items[o].item[0]!==this.currentItem[0]&&(!p||e(this.positionAbs.top+this.offset.click.top,this.items[o].top,this.items[o].height))&&(u=this.items[o].item.offset()[h],d=!1,Math.abs(u-c)>Math.abs(u+this.items[o][l]-c)&&(d=!0,u+=this.items[o][l]),a>Math.abs(u-c)&&(a=Math.abs(u-c),r=this.items[o],this.direction=d?"up":"down"));if(!r&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[g])return;r?this._rearrange(s,r,null,!0):this._rearrange(s,null,this.containers[g].element,!0),this._trigger("change",s,this._uiHash()),this.containers[g]._trigger("change",s,this._uiHash(this)),this.currentContainer=this.containers[g],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[g]._trigger("over",s,this._uiHash(this)),this.containers[g].containerCache.over=1}},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper)?t(i.helper.apply(this.element[0],[e,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||t("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var e=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&t.ui.ie)&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var t=this.currentItem.position();return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:t.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,t("document"===n.containment?document:window).width()-this.helperProportions.width-this.margins.left,(t("document"===n.containment?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(e=t(n.containment)[0],i=t(n.containment).offset(),s="hidden"!==t(e).css("overflow"),this.containment=[i.left+(parseInt(t(e).css("borderLeftWidth"),10)||0)+(parseInt(t(e).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(t(e).css("borderTopWidth"),10)||0)+(parseInt(t(e).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(e.scrollWidth,e.offsetWidth):e.offsetWidth)-(parseInt(t(e).css("borderLeftWidth"),10)||0)-(parseInt(t(e).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(e.scrollHeight,e.offsetHeight):e.offsetHeight)-(parseInt(t(e).css("borderTopWidth"),10)||0)-(parseInt(t(e).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(e,i){i||(i=this.position);var s="absolute"===e?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())*s}},_generatePosition:function(e){var i,s,n=this.options,o=e.pageX,a=e.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==document&&t.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==document&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(e.pageX-this.offset.click.left<this.containment[0]&&(o=this.containment[0]+this.offset.click.left),e.pageY-this.offset.click.top<this.containment[1]&&(a=this.containment[1]+this.offset.click.top),e.pageX-this.offset.click.left>this.containment[2]&&(o=this.containment[2]+this.offset.click.left),e.pageY-this.offset.click.top>this.containment[3]&&(a=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((a-this.originalPageY)/n.grid[1])*n.grid[1],a=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((o-this.originalPageX)/n.grid[0])*n.grid[0],o=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){this.reverting=!1;var i,s=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(i in this._storedCSS)("auto"===this._storedCSS[i]||"static"===this._storedCSS[i])&&(this._storedCSS[i]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!e&&s.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||s.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(s.push(function(t){this._trigger("remove",t,this._uiHash())}),s.push(function(t){return function(e){t._trigger("receive",e,this._uiHash(this))}}.call(this,this.currentContainer)),s.push(function(t){return function(e){t._trigger("update",e,this._uiHash(this))}}.call(this,this.currentContainer)))),i=this.containers.length-1;i>=0;i--)e||s.push(function(t){return function(e){t._trigger("deactivate",e,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(s.push(function(t){return function(e){t._trigger("out",e,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,this.cancelHelperRemoval){if(!e){for(this._trigger("beforeStop",t,this._uiHash()),i=0;s.length>i;i++)s[i].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}if(e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null,!e){for(i=0;s.length>i;i++)s[i].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){t.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(e){var i=e||this;return{helper:i.helper,placeholder:i.placeholder||t([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:e?e.element:null}}})}(jQuery),function(t,e){var i="ui-effects-";t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("<p>")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function s(e,i){var s,n,a={};for(s in i)n=i[s],e[s]!==n&&(o[s]||(t.fx.step[s]||!isNaN(parseFloat(n)))&&(a[s]=n));return a}var n=["add","remove","toggle"],o={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(jQuery.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(e,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var e=t(this);return{el:e,start:i(this)}}),o=function(){t.each(n,function(t,i){e[i]&&a[i+"Class"](e[i])})},o(),l=l.map(function(){return this.end=i(this.el[0]),this.diff=s(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(i){return function(s,n,o,a,r){return"boolean"==typeof n||n===e?o?t.effects.animateClass.call(this,n?{add:s}:{remove:s},o,a,r):i.apply(this,arguments):t.effects.animateClass.call(this,{toggle:s},n,o,a)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function s(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function n(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}t.extend(t.effects,{version:"1.10.2",save:function(t,e){for(var s=0;e.length>s;s++)null!==e[s]&&t.data(i+e[s],t[0].style[e[s]])},restore:function(t,s){var n,o;for(o=0;s.length>o;o++)null!==s[o]&&(n=t.data(i+s[o]),n===e&&(n=""),t.css(s[o],n))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).focus(),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).focus()),e},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function e(e){function s(){t.isFunction(o)&&o.call(n[0]),t.isFunction(e)&&e()}var n=t(this),o=i.complete,r=i.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),s()):a.call(n[0],i,s)}var i=s.apply(this,arguments),n=i.mode,o=i.queue,a=t.effects.effect[i.effect];return t.fx.off||!a?n?this[n](i.duration,i.complete):this.each(function(){i.complete&&i.complete.call(this)}):o===!1?this.each(e):this.queue(o||"fx",e)},show:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="show",this.effect.call(this,i)}}(t.fn.show),hide:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="hide",this.effect.call(this,i)}}(t.fn.hide),toggle:function(t){return function(e){if(n(e)||"boolean"==typeof e)return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="toggle",this.effect.call(this,i)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s}})}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}()}(jQuery),function(t){var e=0,i={},s={};i.height=i.paddingTop=i.paddingBottom=i.borderTopWidth=i.borderBottomWidth="hide",s.height=s.paddingTop=s.paddingBottom=s.borderTopWidth=s.borderBottomWidth="show",t.widget("ui.accordion",{version:"1.10.2",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t(),content:this.active.length?this.active.next():t()}},_createIcons:function(){var e=this.options.icons;e&&(t("<span>").addClass("ui-accordion-header-icon ui-icon "+e.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(e.header).addClass(e.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove() +},_destroy:function(){var t;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),undefined):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),"disabled"===t&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!e),undefined)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),o.focus(),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().focus()},refresh:function(){var e=this.options;this._processPanels(),(e.active===!1&&e.collapsible===!0||!this.headers.length)&&(e.active=!1,this.active=t()),e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide()},_refresh:function(){var i,s=this.options,n=s.heightStyle,o=this.element.parent(),a=this.accordionId="ui-accordion-"+(this.element.attr("id")||++e);this.active=this._findActive(s.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(e){var i=t(this),s=i.attr("id"),n=i.next(),o=n.attr("id");s||(s=a+"-header-"+e,i.attr("id",s)),o||(o=a+"-panel-"+e,n.attr("id",o)),i.attr("aria-controls",o),n.attr("aria-labelledby",s)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false",tabIndex:-1}).next().attr({"aria-expanded":"false","aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true",tabIndex:0}).next().attr({"aria-expanded":"true","aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(s.event),"fill"===n?(i=o.height(),this.element.siblings(":visible").each(function(){var e=t(this),s=e.css("position");"absolute"!==s&&"fixed"!==s&&(i-=e.outerHeight(!0))}),this.headers.each(function(){i-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,i-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===n&&(i=0,this.headers.next().each(function(){i=Math.max(i,t(this).css("height","").height())}).height(i))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i=this.options,s=this.active,n=t(e.currentTarget),o=n[0]===s[0],a=o&&i.collapsible,r=a?t():n.next(),h=s.next(),l={oldHeader:s,oldPanel:h,newHeader:a?t():n,newPanel:r};e.preventDefault(),o&&!i.collapsible||this._trigger("beforeActivate",e,l)===!1||(i.active=a?!1:this.headers.index(n),this.active=o?t():n,this._toggle(l),s.removeClass("ui-accordion-header-active ui-state-active"),i.icons&&s.children(".ui-accordion-header-icon").removeClass(i.icons.activeHeader).addClass(i.icons.header),o||(n.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),i.icons&&n.children(".ui-accordion-header-icon").removeClass(i.icons.header).addClass(i.icons.activeHeader),n.next().addClass("ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-expanded":"false","aria-hidden":"true"}),s.prev().attr("aria-selected","false"),i.length&&s.length?s.prev().attr("tabIndex",-1):i.length&&this.headers.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),i.attr({"aria-expanded":"true","aria-hidden":"false"}).prev().attr({"aria-selected":"true",tabIndex:0})},_animate:function(t,e,n){var o,a,r,h=this,l=0,c=t.length&&(!e.length||t.index()<e.index()),u=this.options.animate||{},d=c&&u.down||u,p=function(){h._toggleComplete(n)};return"number"==typeof d&&(r=d),"string"==typeof d&&(a=d),a=a||d.easing||u.easing,r=r||d.duration||u.duration,e.length?t.length?(o=t.show().outerHeight(),e.animate(i,{duration:r,easing:a,step:function(t,e){e.now=Math.round(t)}}),t.hide().animate(s,{duration:r,easing:a,complete:p,step:function(t,i){i.now=Math.round(t),"height"!==i.prop?l+=i.now:"content"!==h.options.heightStyle&&(i.now=Math.round(o-e.outerHeight()-l),l=0)}}),undefined):e.animate(i,r,a,p):t.animate(s,r,a,p)},_toggleComplete:function(t){var e=t.oldPanel;e.removeClass("ui-accordion-content-active").prev().removeClass("ui-corner-top").addClass("ui-corner-all"),e.length&&(e.parent()[0].className=e.parent()[0].className),this._trigger("activate",null,t)}})}(jQuery),function(t){var e=0;t.widget("ui.autocomplete",{version:"1.10.2",defaultElement:"<input>",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n;this.isMultiLine=o?!0:a?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,undefined;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:case o.NUMPAD_ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,s.preventDefault(),undefined;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),undefined):(this._searchTimeout(t),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(t),this._change(t),undefined)}}),this._initSource(),this.menu=t("<ul>").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({input:t(),role:null}).hide().data("ui-menu"),this._on(this.menu.element,{mousedown:function(e){e.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var i=this.menu.element[0];t(e.target).closest(".ui-menu-item").length||this._delay(function(){var e=this;this.document.one("mousedown",function(s){s.target===e.element[0]||s.target===i||t.contains(i,s.target)||e.close()})})},menufocus:function(e,i){if(this.isNewMenu&&(this.isNewMenu=!1,e.originalEvent&&/^mouse/.test(e.originalEvent.type)))return this.menu.blur(),this.document.one("mousemove",function(){t(e.target).trigger(e.originalEvent)}),undefined;var s=i.item.data("ui-autocomplete-item");!1!==this._trigger("focus",e,{item:s})?e.originalEvent&&/^key/.test(e.originalEvent.type)&&this._value(s.value):this.liveRegion.text(s.value)},menuselect:function(t,e){var i=e.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",t,{item:i})&&this._value(i.value),this.term=this._value(),this.close(t),this.selectedItem=i}}),this.liveRegion=t("<span>",{role:"status","aria-live":"polite"}).addClass("ui-helper-hidden-accessible").insertAfter(this.element),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e||(e=this.element.closest(".ui-front")),e.length||(e=this.document[0].body),e},_initSource:function(){var e,i,s=this;t.isArray(this.options.source)?(e=this.options.source,this.source=function(i,s){s(t.ui.autocomplete.filter(e,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(e,n){s.xhr&&s.xhr.abort(),s.xhr=t.ajax({url:i,data:e,dataType:"json",success:function(t){n(t)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(t){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,t))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length<this.options.minLength?this.close(e):this._trigger("search",e)!==!1?this._search(t):undefined},_search:function(t){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:t},this._response())},_response:function(){var t=this,i=++e;return function(s){i===e&&t.__response(s),t.pending--,t.pending||t.element.removeClass("ui-autocomplete-loading")}},__response:function(t){t&&(t=this._normalize(t)),this._trigger("response",null,{content:t}),!this.options.disabled&&t&&t.length&&!this.cancelSearch?(this._suggest(t),this._trigger("open")):this._close()},close:function(t){this.cancelSearch=!0,this._close(t)},_close:function(t){this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",t))},_change:function(t){this.previous!==this._value()&&this._trigger("change",t,{item:this.selectedItem})},_normalize:function(e){return e.length&&e[0].label&&e[0].value?e:t.map(e,function(e){return"string"==typeof e?{label:e,value:e}:t.extend({label:e.label||e.value,value:e.value||e.label},e)})},_suggest:function(e){var i=this.menu.element.empty();this._renderMenu(i,e),this.isNewMenu=!0,this.menu.refresh(),i.show(),this._resizeMenu(),i.position(t.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next()},_resizeMenu:function(){var t=this.menu.element;t.outerWidth(Math.max(t.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(e,i){var s=this;t.each(i,function(t,i){s._renderItemData(e,i)})},_renderItemData:function(t,e){return this._renderItem(t,e).data("ui-autocomplete-item",e)},_renderItem:function(e,i){return t("<li>").append(t("<a>").text(i.label)).appendTo(e)},_move:function(t,e){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this._value(this.term),this.menu.blur(),undefined):(this.menu[t](e),undefined):(this.search(null,e),undefined)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(t,e),e.preventDefault())}}),t.extend(t.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(e,i){var s=RegExp(t.ui.autocomplete.escapeRegex(i),"i");return t.grep(e,function(t){return s.test(t.label||t.value||t)})}}),t.widget("ui.autocomplete",t.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(t>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(t){var e;this._superApply(arguments),this.options.disabled||this.cancelSearch||(e=t&&t.length?this.options.messages.results(t.length):this.options.messages.noResults,this.liveRegion.text(e))}})}(jQuery),function(t){var e,i,s,n,o="ui-button ui-widget ui-state-default ui-corner-all",a="ui-state-hover ui-state-active ",r="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",h=function(){var e=t(this).find(":ui-button");setTimeout(function(){e.button("refresh")},1)},l=function(e){var i=e.name,s=e.form,n=t([]);return i&&(i=i.replace(/'/g,"\\'"),n=s?t(s).find("[name='"+i+"']"):t("[name='"+i+"']",e.ownerDocument).filter(function(){return!this.form})),n};t.widget("ui.button",{version:"1.10.2",defaultElement:"<button>",options:{disabled:null,text:!0,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset"+this.eventNamespace).bind("reset"+this.eventNamespace,h),"boolean"!=typeof this.options.disabled?this.options.disabled=!!this.element.prop("disabled"):this.element.prop("disabled",this.options.disabled),this._determineButtonType(),this.hasTitle=!!this.buttonElement.attr("title");var a=this,r=this.options,c="checkbox"===this.type||"radio"===this.type,u=c?"":"ui-state-active",d="ui-state-focus";null===r.label&&(r.label="input"===this.type?this.buttonElement.val():this.buttonElement.html()),this._hoverable(this.buttonElement),this.buttonElement.addClass(o).attr("role","button").bind("mouseenter"+this.eventNamespace,function(){r.disabled||this===e&&t(this).addClass("ui-state-active")}).bind("mouseleave"+this.eventNamespace,function(){r.disabled||t(this).removeClass(u)}).bind("click"+this.eventNamespace,function(t){r.disabled&&(t.preventDefault(),t.stopImmediatePropagation())}),this.element.bind("focus"+this.eventNamespace,function(){a.buttonElement.addClass(d)}).bind("blur"+this.eventNamespace,function(){a.buttonElement.removeClass(d)}),c&&(this.element.bind("change"+this.eventNamespace,function(){n||a.refresh()}),this.buttonElement.bind("mousedown"+this.eventNamespace,function(t){r.disabled||(n=!1,i=t.pageX,s=t.pageY)}).bind("mouseup"+this.eventNamespace,function(t){r.disabled||(i!==t.pageX||s!==t.pageY)&&(n=!0)})),"checkbox"===this.type?this.buttonElement.bind("click"+this.eventNamespace,function(){return r.disabled||n?!1:undefined}):"radio"===this.type?this.buttonElement.bind("click"+this.eventNamespace,function(){if(r.disabled||n)return!1;t(this).addClass("ui-state-active"),a.buttonElement.attr("aria-pressed","true");var e=a.element[0];l(e).not(e).map(function(){return t(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed","false")}):(this.buttonElement.bind("mousedown"+this.eventNamespace,function(){return r.disabled?!1:(t(this).addClass("ui-state-active"),e=this,a.document.one("mouseup",function(){e=null}),undefined)}).bind("mouseup"+this.eventNamespace,function(){return r.disabled?!1:(t(this).removeClass("ui-state-active"),undefined)}).bind("keydown"+this.eventNamespace,function(e){return r.disabled?!1:((e.keyCode===t.ui.keyCode.SPACE||e.keyCode===t.ui.keyCode.ENTER)&&t(this).addClass("ui-state-active"),undefined)}).bind("keyup"+this.eventNamespace+" blur"+this.eventNamespace,function(){t(this).removeClass("ui-state-active")}),this.buttonElement.is("a")&&this.buttonElement.keyup(function(e){e.keyCode===t.ui.keyCode.SPACE&&t(this).click()})),this._setOption("disabled",r.disabled),this._resetButton()},_determineButtonType:function(){var t,e,i;this.type=this.element.is("[type=checkbox]")?"checkbox":this.element.is("[type=radio]")?"radio":this.element.is("input")?"input":"button","checkbox"===this.type||"radio"===this.type?(t=this.element.parents().last(),e="label[for='"+this.element.attr("id")+"']",this.buttonElement=t.find(e),this.buttonElement.length||(t=t.length?t.siblings():this.element.siblings(),this.buttonElement=t.filter(e),this.buttonElement.length||(this.buttonElement=t.find(e))),this.element.addClass("ui-helper-hidden-accessible"),i=this.element.is(":checked"),i&&this.buttonElement.addClass("ui-state-active"),this.buttonElement.prop("aria-pressed",i)):this.buttonElement=this.element},widget:function(){return this.buttonElement},_destroy:function(){this.element.removeClass("ui-helper-hidden-accessible"),this.buttonElement.removeClass(o+" "+a+" "+r).removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html()),this.hasTitle||this.buttonElement.removeAttr("title")},_setOption:function(t,e){return this._super(t,e),"disabled"===t?(e?this.element.prop("disabled",!0):this.element.prop("disabled",!1),undefined):(this._resetButton(),undefined)},refresh:function(){var e=this.element.is("input, button")?this.element.is(":disabled"):this.element.hasClass("ui-button-disabled");e!==this.options.disabled&&this._setOption("disabled",e),"radio"===this.type?l(this.element[0]).each(function(){t(this).is(":checked")?t(this).button("widget").addClass("ui-state-active").attr("aria-pressed","true"):t(this).button("widget").removeClass("ui-state-active").attr("aria-pressed","false")}):"checkbox"===this.type&&(this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed","true"):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed","false"))},_resetButton:function(){if("input"===this.type)return this.options.label&&this.element.val(this.options.label),undefined;var e=this.buttonElement.removeClass(r),i=t("<span></span>",this.document[0]).addClass("ui-button-text").html(this.options.label).appendTo(e.empty()).text(),s=this.options.icons,n=s.primary&&s.secondary,o=[];s.primary||s.secondary?(this.options.text&&o.push("ui-button-text-icon"+(n?"s":s.primary?"-primary":"-secondary")),s.primary&&e.prepend("<span class='ui-button-icon-primary ui-icon "+s.primary+"'></span>"),s.secondary&&e.append("<span class='ui-button-icon-secondary ui-icon "+s.secondary+"'></span>"),this.options.text||(o.push(n?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||e.attr("title",t.trim(i)))):o.push("ui-button-text-only"),e.addClass(o.join(" "))}}),t.widget("ui.buttonset",{version:"1.10.2",options:{items:"button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(ui-button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(t,e){"disabled"===t&&this.buttons.button("option",t,e),this._super(t,e)},refresh:function(){var e="rtl"===this.element.css("direction");this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return t(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(e?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(e?"ui-corner-left":"ui-corner-right").end().end()},_destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return t(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy")}})}(jQuery),function(t,e){function i(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.dpDiv=s(t("<div id='"+this._mainDivId+"' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"))}function s(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.delegate(i,"mouseout",function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).delegate(i,"mouseover",function(){t.datepicker._isDisabledDatepicker(o.inline?e.parent()[0]:o.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))})}function n(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}t.extend(t.ui,{datepicker:{version:"1.10.2"}});var o,a="datepicker",r=(new Date).getTime();t.extend(i.prototype,{markerClassName:"hasDatepicker",maxRows:4,_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(t){return n(this._defaults,t||{}),this},_attachDatepicker:function(e,i){var s,n,o;s=e.nodeName.toLowerCase(),n="div"===s||"span"===s,e.id||(this.uuid+=1,e.id="dp"+this.uuid),o=this._newInst(t(e),n),o.settings=t.extend({},i||{}),"input"===s?this._connectDatepicker(e,o):n&&this._inlineDatepicker(e,o)},_newInst:function(e,i){var n=e[0].id.replace(/([^A-Za-z0-9_\-])/g,"\\\\$1");return{id:n,input:e,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:i,dpDiv:i?s(t("<div class='"+this._inlineClass+" ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")):this.dpDiv}},_connectDatepicker:function(e,i){var s=t(e);i.append=t([]),i.trigger=t([]),s.hasClass(this.markerClassName)||(this._attachments(s,i),s.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp),this._autoSize(i),t.data(e,a,i),i.settings.disabled&&this._disableDatepicker(e))},_attachments:function(e,i){var s,n,o,a=this._get(i,"appendText"),r=this._get(i,"isRTL");i.append&&i.append.remove(),a&&(i.append=t("<span class='"+this._appendClass+"'>"+a+"</span>"),e[r?"before":"after"](i.append)),e.unbind("focus",this._showDatepicker),i.trigger&&i.trigger.remove(),s=this._get(i,"showOn"),("focus"===s||"both"===s)&&e.focus(this._showDatepicker),("button"===s||"both"===s)&&(n=this._get(i,"buttonText"),o=this._get(i,"buttonImage"),i.trigger=t(this._get(i,"buttonImageOnly")?t("<img/>").addClass(this._triggerClass).attr({src:o,alt:n,title:n}):t("<button type='button'></button>").addClass(this._triggerClass).html(o?t("<img/>").attr({src:o,alt:n,title:n}):n)),e[r?"before":"after"](i.trigger),i.trigger.click(function(){return t.datepicker._datepickerShowing&&t.datepicker._lastInput===e[0]?t.datepicker._hideDatepicker():t.datepicker._datepickerShowing&&t.datepicker._lastInput!==e[0]?(t.datepicker._hideDatepicker(),t.datepicker._showDatepicker(e[0])):t.datepicker._showDatepicker(e[0]),!1}))},_autoSize:function(t){if(this._get(t,"autoSize")&&!t.inline){var e,i,s,n,o=new Date(2009,11,20),a=this._get(t,"dateFormat");a.match(/[DM]/)&&(e=function(t){for(i=0,s=0,n=0;t.length>n;n++)t[n].length>i&&(i=t[n].length,s=n);return s},o.setMonth(e(this._get(t,a.match(/MM/)?"monthNames":"monthNamesShort"))),o.setDate(e(this._get(t,a.match(/DD/)?"dayNames":"dayNamesShort"))+20-o.getDay())),t.input.attr("size",this._formatDate(t,o).length)}},_inlineDatepicker:function(e,i){var s=t(e);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),t.data(e,a,i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(e),i.dpDiv.css("display","block"))},_dialogDatepicker:function(e,i,s,o,r){var h,l,c,u,d,p=this._dialogInst;return p||(this.uuid+=1,h="dp"+this.uuid,this._dialogInput=t("<input type='text' id='"+h+"' style='position: absolute; top: -100px; width: 0px;'/>"),this._dialogInput.keydown(this._doKeyDown),t("body").append(this._dialogInput),p=this._dialogInst=this._newInst(this._dialogInput,!1),p.settings={},t.data(this._dialogInput[0],a,p)),n(p.settings,o||{}),i=i&&i.constructor===Date?this._formatDate(p,i):i,this._dialogInput.val(i),this._pos=r?r.length?r:[r.pageX,r.pageY]:null,this._pos||(l=document.documentElement.clientWidth,c=document.documentElement.clientHeight,u=document.documentElement.scrollLeft||document.body.scrollLeft,d=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[l/2-100+u,c/2-150+d]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),p.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),t.blockUI&&t.blockUI(this.dpDiv),t.data(this._dialogInput[0],a,p),this},_destroyDatepicker:function(e){var i,s=t(e),n=t.data(e,a);s.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),t.removeData(e,a),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty())},_enableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,a);n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!1,o.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}))},_disableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,a);n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!0,o.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}),this._disabledInputs[this._disabledInputs.length]=e)},_isDisabledDatepicker:function(t){if(!t)return!1;for(var e=0;this._disabledInputs.length>e;e++)if(this._disabledInputs[e]===t)return!0;return!1},_getInst:function(e){try{return t.data(e,a)}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(i,s,o){var a,r,h,l,c=this._getInst(i);return 2===arguments.length&&"string"==typeof s?"defaults"===s?t.extend({},t.datepicker._defaults):c?"all"===s?t.extend({},c.settings):this._get(c,s):null:(a=s||{},"string"==typeof s&&(a={},a[s]=o),c&&(this._curInst===c&&this._hideDatepicker(),r=this._getDateDatepicker(i,!0),h=this._getMinMaxDate(c,"min"),l=this._getMinMaxDate(c,"max"),n(c.settings,a),null!==h&&a.dateFormat!==e&&a.minDate===e&&(c.settings.minDate=this._formatDate(c,h)),null!==l&&a.dateFormat!==e&&a.maxDate===e&&(c.settings.maxDate=this._formatDate(c,l)),"disabled"in a&&(a.disabled?this._disableDatepicker(i):this._enableDatepicker(i)),this._attachments(t(i),c),this._autoSize(c),this._setDate(c,r),this._updateAlternate(c),this._updateDatepicker(c)),e)},_changeDatepicker:function(t,e,i){this._optionDatepicker(t,e,i)},_refreshDatepicker:function(t){var e=this._getInst(t);e&&this._updateDatepicker(e)},_setDateDatepicker:function(t,e){var i=this._getInst(t);i&&(this._setDate(i,e),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(t,e){var i=this._getInst(t);return i&&!i.inline&&this._setDateFromField(i,e),i?this._getDate(i):null},_doKeyDown:function(e){var i,s,n,o=t.datepicker._getInst(e.target),a=!0,r=o.dpDiv.is(".ui-datepicker-rtl");if(o._keyEvent=!0,t.datepicker._datepickerShowing)switch(e.keyCode){case 9:t.datepicker._hideDatepicker(),a=!1;break;case 13:return n=t("td."+t.datepicker._dayOverClass+":not(."+t.datepicker._currentClass+")",o.dpDiv),n[0]&&t.datepicker._selectDay(e.target,o.selectedMonth,o.selectedYear,n[0]),i=t.datepicker._get(o,"onSelect"),i?(s=t.datepicker._formatDate(o),i.apply(o.input?o.input[0]:null,[s,o])):t.datepicker._hideDatepicker(),!1;case 27:t.datepicker._hideDatepicker();break;case 33:t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M"); +break;case 34:t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 35:(e.ctrlKey||e.metaKey)&&t.datepicker._clearDate(e.target),a=e.ctrlKey||e.metaKey;break;case 36:(e.ctrlKey||e.metaKey)&&t.datepicker._gotoToday(e.target),a=e.ctrlKey||e.metaKey;break;case 37:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?1:-1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 38:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,-7,"D"),a=e.ctrlKey||e.metaKey;break;case 39:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?-1:1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 40:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,7,"D"),a=e.ctrlKey||e.metaKey;break;default:a=!1}else 36===e.keyCode&&e.ctrlKey?t.datepicker._showDatepicker(this):a=!1;a&&(e.preventDefault(),e.stopPropagation())},_doKeyPress:function(i){var s,n,o=t.datepicker._getInst(i.target);return t.datepicker._get(o,"constrainInput")?(s=t.datepicker._possibleChars(t.datepicker._get(o,"dateFormat")),n=String.fromCharCode(null==i.charCode?i.keyCode:i.charCode),i.ctrlKey||i.metaKey||" ">n||!s||s.indexOf(n)>-1):e},_doKeyUp:function(e){var i,s=t.datepicker._getInst(e.target);if(s.input.val()!==s.lastVal)try{i=t.datepicker.parseDate(t.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,t.datepicker._getFormatConfig(s)),i&&(t.datepicker._setDateFromField(s),t.datepicker._updateAlternate(s),t.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(e){if(e=e.target||e,"input"!==e.nodeName.toLowerCase()&&(e=t("input",e.parentNode)[0]),!t.datepicker._isDisabledDatepicker(e)&&t.datepicker._lastInput!==e){var i,s,o,a,r,h,l;i=t.datepicker._getInst(e),t.datepicker._curInst&&t.datepicker._curInst!==i&&(t.datepicker._curInst.dpDiv.stop(!0,!0),i&&t.datepicker._datepickerShowing&&t.datepicker._hideDatepicker(t.datepicker._curInst.input[0])),s=t.datepicker._get(i,"beforeShow"),o=s?s.apply(e,[e,i]):{},o!==!1&&(n(i.settings,o),i.lastVal=null,t.datepicker._lastInput=e,t.datepicker._setDateFromField(i),t.datepicker._inDialog&&(e.value=""),t.datepicker._pos||(t.datepicker._pos=t.datepicker._findPos(e),t.datepicker._pos[1]+=e.offsetHeight),a=!1,t(e).parents().each(function(){return a|="fixed"===t(this).css("position"),!a}),r={left:t.datepicker._pos[0],top:t.datepicker._pos[1]},t.datepicker._pos=null,i.dpDiv.empty(),i.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),t.datepicker._updateDatepicker(i),r=t.datepicker._checkOffset(i,r,a),i.dpDiv.css({position:t.datepicker._inDialog&&t.blockUI?"static":a?"fixed":"absolute",display:"none",left:r.left+"px",top:r.top+"px"}),i.inline||(h=t.datepicker._get(i,"showAnim"),l=t.datepicker._get(i,"duration"),i.dpDiv.zIndex(t(e).zIndex()+1),t.datepicker._datepickerShowing=!0,t.effects&&t.effects.effect[h]?i.dpDiv.show(h,t.datepicker._get(i,"showOptions"),l):i.dpDiv[h||"show"](h?l:null),i.input.is(":visible")&&!i.input.is(":disabled")&&i.input.focus(),t.datepicker._curInst=i))}},_updateDatepicker:function(e){this.maxRows=4,o=e,e.dpDiv.empty().append(this._generateHTML(e)),this._attachHandlers(e),e.dpDiv.find("."+this._dayOverClass+" a").mouseover();var i,s=this._getNumberOfMonths(e),n=s[1],a=17;e.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&e.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),e.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),e.dpDiv[(this._get(e,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),e===t.datepicker._curInst&&t.datepicker._datepickerShowing&&e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&e.input[0]!==document.activeElement&&e.input.focus(),e.yearshtml&&(i=e.yearshtml,setTimeout(function(){i===e.yearshtml&&e.yearshtml&&e.dpDiv.find("select.ui-datepicker-year:first").replaceWith(e.yearshtml),i=e.yearshtml=null},0))},_getBorders:function(t){var e=function(t){return{thin:1,medium:2,thick:3}[t]||t};return[parseFloat(e(t.css("border-left-width"))),parseFloat(e(t.css("border-top-width")))]},_checkOffset:function(e,i,s){var n=e.dpDiv.outerWidth(),o=e.dpDiv.outerHeight(),a=e.input?e.input.outerWidth():0,r=e.input?e.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:t(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:t(document).scrollTop());return i.left-=this._get(e,"isRTL")?n-a:0,i.left-=s&&i.left===e.input.offset().left?t(document).scrollLeft():0,i.top-=s&&i.top===e.input.offset().top+r?t(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+o>l&&l>o?Math.abs(o+r):0),i},_findPos:function(e){for(var i,s=this._getInst(e),n=this._get(s,"isRTL");e&&("hidden"===e.type||1!==e.nodeType||t.expr.filters.hidden(e));)e=e[n?"previousSibling":"nextSibling"];return i=t(e).offset(),[i.left,i.top]},_hideDatepicker:function(e){var i,s,n,o,r=this._curInst;!r||e&&r!==t.data(e,a)||this._datepickerShowing&&(i=this._get(r,"showAnim"),s=this._get(r,"duration"),n=function(){t.datepicker._tidyDialog(r)},t.effects&&(t.effects.effect[i]||t.effects[i])?r.dpDiv.hide(i,t.datepicker._get(r,"showOptions"),s,n):r.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,o=this._get(r,"onClose"),o&&o.apply(r.input?r.input[0]:null,[r.input?r.input.val():"",r]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),t.blockUI&&(t.unblockUI(),t("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(t){t.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(e){if(t.datepicker._curInst){var i=t(e.target),s=t.datepicker._getInst(i[0]);(i[0].id!==t.datepicker._mainDivId&&0===i.parents("#"+t.datepicker._mainDivId).length&&!i.hasClass(t.datepicker.markerClassName)&&!i.closest("."+t.datepicker._triggerClass).length&&t.datepicker._datepickerShowing&&(!t.datepicker._inDialog||!t.blockUI)||i.hasClass(t.datepicker.markerClassName)&&t.datepicker._curInst!==s)&&t.datepicker._hideDatepicker()}},_adjustDate:function(e,i,s){var n=t(e),o=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(o,i+("M"===s?this._get(o,"showCurrentAtPos"):0),s),this._updateDatepicker(o))},_gotoToday:function(e){var i,s=t(e),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(e,i,s){var n=t(e),o=this._getInst(n[0]);o["selected"+("M"===s?"Month":"Year")]=o["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(o),this._adjustDate(n)},_selectDay:function(e,i,s,n){var o,a=t(e);t(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(a[0])||(o=this._getInst(a[0]),o.selectedDay=o.currentDay=t("a",n).html(),o.selectedMonth=o.currentMonth=i,o.selectedYear=o.currentYear=s,this._selectDate(e,this._formatDate(o,o.currentDay,o.currentMonth,o.currentYear)))},_clearDate:function(e){var i=t(e);this._selectDate(i,"")},_selectDate:function(e,i){var s,n=t(e),o=this._getInst(n[0]);i=null!=i?i:this._formatDate(o),o.input&&o.input.val(i),this._updateAlternate(o),s=this._get(o,"onSelect"),s?s.apply(o.input?o.input[0]:null,[i,o]):o.input&&o.input.trigger("change"),o.inline?this._updateDatepicker(o):(this._hideDatepicker(),this._lastInput=o.input[0],"object"!=typeof o.input[0]&&o.input.focus(),this._lastInput=null)},_updateAlternate:function(e){var i,s,n,o=this._get(e,"altField");o&&(i=this._get(e,"altFormat")||this._get(e,"dateFormat"),s=this._getDate(e),n=this.formatDate(i,s,this._getFormatConfig(e)),t(o).each(function(){t(this).val(n)}))},noWeekends:function(t){var e=t.getDay();return[e>0&&6>e,""]},iso8601Week:function(t){var e,i=new Date(t.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),e=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((e-i)/864e5)/7)+1},parseDate:function(i,s,n){if(null==i||null==s)throw"Invalid arguments";if(s="object"==typeof s?""+s:s+"",""===s)return null;var o,a,r,h,l=0,c=(n?n.shortYearCutoff:null)||this._defaults.shortYearCutoff,u="string"!=typeof c?c:(new Date).getFullYear()%100+parseInt(c,10),d=(n?n.dayNamesShort:null)||this._defaults.dayNamesShort,p=(n?n.dayNames:null)||this._defaults.dayNames,f=(n?n.monthNamesShort:null)||this._defaults.monthNamesShort,g=(n?n.monthNames:null)||this._defaults.monthNames,m=-1,v=-1,_=-1,b=-1,y=!1,w=function(t){var e=i.length>o+1&&i.charAt(o+1)===t;return e&&o++,e},k=function(t){var e=w(t),i="@"===t?14:"!"===t?20:"y"===t&&e?4:"o"===t?3:2,n=RegExp("^\\d{1,"+i+"}"),o=s.substring(l).match(n);if(!o)throw"Missing number at position "+l;return l+=o[0].length,parseInt(o[0],10)},x=function(i,n,o){var a=-1,r=t.map(w(i)?o:n,function(t,e){return[[e,t]]}).sort(function(t,e){return-(t[1].length-e[1].length)});if(t.each(r,function(t,i){var n=i[1];return s.substr(l,n.length).toLowerCase()===n.toLowerCase()?(a=i[0],l+=n.length,!1):e}),-1!==a)return a+1;throw"Unknown name at position "+l},D=function(){if(s.charAt(l)!==i.charAt(o))throw"Unexpected literal at position "+l;l++};for(o=0;i.length>o;o++)if(y)"'"!==i.charAt(o)||w("'")?D():y=!1;else switch(i.charAt(o)){case"d":_=k("d");break;case"D":x("D",d,p);break;case"o":b=k("o");break;case"m":v=k("m");break;case"M":v=x("M",f,g);break;case"y":m=k("y");break;case"@":h=new Date(k("@")),m=h.getFullYear(),v=h.getMonth()+1,_=h.getDate();break;case"!":h=new Date((k("!")-this._ticksTo1970)/1e4),m=h.getFullYear(),v=h.getMonth()+1,_=h.getDate();break;case"'":w("'")?D():y=!0;break;default:D()}if(s.length>l&&(r=s.substr(l),!/^\s+/.test(r)))throw"Extra/unparsed characters found in date: "+r;if(-1===m?m=(new Date).getFullYear():100>m&&(m+=(new Date).getFullYear()-(new Date).getFullYear()%100+(u>=m?0:-100)),b>-1)for(v=1,_=b;;){if(a=this._getDaysInMonth(m,v-1),a>=_)break;v++,_-=a}if(h=this._daylightSavingAdjust(new Date(m,v-1,_)),h.getFullYear()!==m||h.getMonth()+1!==v||h.getDate()!==_)throw"Invalid date";return h},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(t,e,i){if(!e)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,o=(i?i.dayNames:null)||this._defaults.dayNames,a=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(e){var i=t.length>s+1&&t.charAt(s+1)===e;return i&&s++,i},l=function(t,e,i){var s=""+e;if(h(t))for(;i>s.length;)s="0"+s;return s},c=function(t,e,i,s){return h(t)?s[e]:i[e]},u="",d=!1;if(e)for(s=0;t.length>s;s++)if(d)"'"!==t.charAt(s)||h("'")?u+=t.charAt(s):d=!1;else switch(t.charAt(s)){case"d":u+=l("d",e.getDate(),2);break;case"D":u+=c("D",e.getDay(),n,o);break;case"o":u+=l("o",Math.round((new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime()-new Date(e.getFullYear(),0,0).getTime())/864e5),3);break;case"m":u+=l("m",e.getMonth()+1,2);break;case"M":u+=c("M",e.getMonth(),a,r);break;case"y":u+=h("y")?e.getFullYear():(10>e.getYear()%100?"0":"")+e.getYear()%100;break;case"@":u+=e.getTime();break;case"!":u+=1e4*e.getTime()+this._ticksTo1970;break;case"'":h("'")?u+="'":d=!0;break;default:u+=t.charAt(s)}return u},_possibleChars:function(t){var e,i="",s=!1,n=function(i){var s=t.length>e+1&&t.charAt(e+1)===i;return s&&e++,s};for(e=0;t.length>e;e++)if(s)"'"!==t.charAt(e)||n("'")?i+=t.charAt(e):s=!1;else switch(t.charAt(e)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=t.charAt(e)}return i},_get:function(t,i){return t.settings[i]!==e?t.settings[i]:this._defaults[i]},_setDateFromField:function(t,e){if(t.input.val()!==t.lastVal){var i=this._get(t,"dateFormat"),s=t.lastVal=t.input?t.input.val():null,n=this._getDefaultDate(t),o=n,a=this._getFormatConfig(t);try{o=this.parseDate(i,s,a)||n}catch(r){s=e?"":s}t.selectedDay=o.getDate(),t.drawMonth=t.selectedMonth=o.getMonth(),t.drawYear=t.selectedYear=o.getFullYear(),t.currentDay=s?o.getDate():0,t.currentMonth=s?o.getMonth():0,t.currentYear=s?o.getFullYear():0,this._adjustInstDate(t)}},_getDefaultDate:function(t){return this._restrictMinMax(t,this._determineDate(t,this._get(t,"defaultDate"),new Date))},_determineDate:function(e,i,s){var n=function(t){var e=new Date;return e.setDate(e.getDate()+t),e},o=function(i){try{return t.datepicker.parseDate(t.datepicker._get(e,"dateFormat"),i,t.datepicker._getFormatConfig(e))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?t.datepicker._getDate(e):null)||new Date,o=n.getFullYear(),a=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":a+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a));break;case"y":case"Y":o+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a))}l=h.exec(i)}return new Date(o,a,r)},a=null==i||""===i?s:"string"==typeof i?o(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return a=a&&"Invalid Date"==""+a?s:a,a&&(a.setHours(0),a.setMinutes(0),a.setSeconds(0),a.setMilliseconds(0)),this._daylightSavingAdjust(a)},_daylightSavingAdjust:function(t){return t?(t.setHours(t.getHours()>12?t.getHours()+2:0),t):null},_setDate:function(t,e,i){var s=!e,n=t.selectedMonth,o=t.selectedYear,a=this._restrictMinMax(t,this._determineDate(t,e,new Date));t.selectedDay=t.currentDay=a.getDate(),t.drawMonth=t.selectedMonth=t.currentMonth=a.getMonth(),t.drawYear=t.selectedYear=t.currentYear=a.getFullYear(),n===t.selectedMonth&&o===t.selectedYear||i||this._notifyChange(t),this._adjustInstDate(t),t.input&&t.input.val(s?"":this._formatDate(t))},_getDate:function(t){var e=!t.currentYear||t.input&&""===t.input.val()?null:this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return e},_attachHandlers:function(e){var i=this._get(e,"stepMonths"),s="#"+e.id.replace(/\\\\/g,"\\");e.dpDiv.find("[data-handler]").map(function(){var e={prev:function(){window["DP_jQuery_"+r].datepicker._adjustDate(s,-i,"M")},next:function(){window["DP_jQuery_"+r].datepicker._adjustDate(s,+i,"M")},hide:function(){window["DP_jQuery_"+r].datepicker._hideDatepicker()},today:function(){window["DP_jQuery_"+r].datepicker._gotoToday(s)},selectDay:function(){return window["DP_jQuery_"+r].datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return window["DP_jQuery_"+r].datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return window["DP_jQuery_"+r].datepicker._selectMonthYear(s,this,"Y"),!1}};t(this).bind(this.getAttribute("data-event"),e[this.getAttribute("data-handler")])})},_generateHTML:function(t){var e,i,s,n,o,a,r,h,l,c,u,d,p,f,g,m,v,_,b,y,w,k,x,D,C,I,P,T,M,S,z,A,H,N,E,W,O,F,R,j=new Date,L=this._daylightSavingAdjust(new Date(j.getFullYear(),j.getMonth(),j.getDate())),Y=this._get(t,"isRTL"),B=this._get(t,"showButtonPanel"),V=this._get(t,"hideIfNoPrevNext"),K=this._get(t,"navigationAsDateFormat"),U=this._getNumberOfMonths(t),q=this._get(t,"showCurrentAtPos"),Q=this._get(t,"stepMonths"),X=1!==U[0]||1!==U[1],$=this._daylightSavingAdjust(t.currentDay?new Date(t.currentYear,t.currentMonth,t.currentDay):new Date(9999,9,9)),G=this._getMinMaxDate(t,"min"),J=this._getMinMaxDate(t,"max"),Z=t.drawMonth-q,te=t.drawYear;if(0>Z&&(Z+=12,te--),J)for(e=this._daylightSavingAdjust(new Date(J.getFullYear(),J.getMonth()-U[0]*U[1]+1,J.getDate())),e=G&&G>e?G:e;this._daylightSavingAdjust(new Date(te,Z,1))>e;)Z--,0>Z&&(Z=11,te--);for(t.drawMonth=Z,t.drawYear=te,i=this._get(t,"prevText"),i=K?this.formatDate(i,this._daylightSavingAdjust(new Date(te,Z-Q,1)),this._getFormatConfig(t)):i,s=this._canAdjustMonth(t,-1,te,Z)?"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click' title='"+i+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"e":"w")+"'>"+i+"</span></a>":V?"":"<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+i+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"e":"w")+"'>"+i+"</span></a>",n=this._get(t,"nextText"),n=K?this.formatDate(n,this._daylightSavingAdjust(new Date(te,Z+Q,1)),this._getFormatConfig(t)):n,o=this._canAdjustMonth(t,1,te,Z)?"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click' title='"+n+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"w":"e")+"'>"+n+"</span></a>":V?"":"<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+n+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"w":"e")+"'>"+n+"</span></a>",a=this._get(t,"currentText"),r=this._get(t,"gotoCurrent")&&t.currentDay?$:L,a=K?this.formatDate(a,r,this._getFormatConfig(t)):a,h=t.inline?"":"<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>"+this._get(t,"closeText")+"</button>",l=B?"<div class='ui-datepicker-buttonpane ui-widget-content'>"+(Y?h:"")+(this._isInRange(t,r)?"<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'>"+a+"</button>":"")+(Y?"":h)+"</div>":"",c=parseInt(this._get(t,"firstDay"),10),c=isNaN(c)?0:c,u=this._get(t,"showWeek"),d=this._get(t,"dayNames"),p=this._get(t,"dayNamesMin"),f=this._get(t,"monthNames"),g=this._get(t,"monthNamesShort"),m=this._get(t,"beforeShowDay"),v=this._get(t,"showOtherMonths"),_=this._get(t,"selectOtherMonths"),b=this._getDefaultDate(t),y="",k=0;U[0]>k;k++){for(x="",this.maxRows=4,D=0;U[1]>D;D++){if(C=this._daylightSavingAdjust(new Date(te,Z,t.selectedDay)),I=" ui-corner-all",P="",X){if(P+="<div class='ui-datepicker-group",U[1]>1)switch(D){case 0:P+=" ui-datepicker-group-first",I=" ui-corner-"+(Y?"right":"left");break;case U[1]-1:P+=" ui-datepicker-group-last",I=" ui-corner-"+(Y?"left":"right");break;default:P+=" ui-datepicker-group-middle",I=""}P+="'>"}for(P+="<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix"+I+"'>"+(/all|left/.test(I)&&0===k?Y?o:s:"")+(/all|right/.test(I)&&0===k?Y?s:o:"")+this._generateMonthYearHeader(t,Z,te,G,J,k>0||D>0,f,g)+"</div><table class='ui-datepicker-calendar'><thead>"+"<tr>",T=u?"<th class='ui-datepicker-week-col'>"+this._get(t,"weekHeader")+"</th>":"",w=0;7>w;w++)M=(w+c)%7,T+="<th"+((w+c+6)%7>=5?" class='ui-datepicker-week-end'":"")+">"+"<span title='"+d[M]+"'>"+p[M]+"</span></th>";for(P+=T+"</tr></thead><tbody>",S=this._getDaysInMonth(te,Z),te===t.selectedYear&&Z===t.selectedMonth&&(t.selectedDay=Math.min(t.selectedDay,S)),z=(this._getFirstDayOfMonth(te,Z)-c+7)%7,A=Math.ceil((z+S)/7),H=X?this.maxRows>A?this.maxRows:A:A,this.maxRows=H,N=this._daylightSavingAdjust(new Date(te,Z,1-z)),E=0;H>E;E++){for(P+="<tr>",W=u?"<td class='ui-datepicker-week-col'>"+this._get(t,"calculateWeek")(N)+"</td>":"",w=0;7>w;w++)O=m?m.apply(t.input?t.input[0]:null,[N]):[!0,""],F=N.getMonth()!==Z,R=F&&!_||!O[0]||G&&G>N||J&&N>J,W+="<td class='"+((w+c+6)%7>=5?" ui-datepicker-week-end":"")+(F?" ui-datepicker-other-month":"")+(N.getTime()===C.getTime()&&Z===t.selectedMonth&&t._keyEvent||b.getTime()===N.getTime()&&b.getTime()===C.getTime()?" "+this._dayOverClass:"")+(R?" "+this._unselectableClass+" ui-state-disabled":"")+(F&&!v?"":" "+O[1]+(N.getTime()===$.getTime()?" "+this._currentClass:"")+(N.getTime()===L.getTime()?" ui-datepicker-today":""))+"'"+(F&&!v||!O[2]?"":" title='"+O[2].replace(/'/g,"'")+"'")+(R?"":" data-handler='selectDay' data-event='click' data-month='"+N.getMonth()+"' data-year='"+N.getFullYear()+"'")+">"+(F&&!v?" ":R?"<span class='ui-state-default'>"+N.getDate()+"</span>":"<a class='ui-state-default"+(N.getTime()===L.getTime()?" ui-state-highlight":"")+(N.getTime()===$.getTime()?" ui-state-active":"")+(F?" ui-priority-secondary":"")+"' href='#'>"+N.getDate()+"</a>")+"</td>",N.setDate(N.getDate()+1),N=this._daylightSavingAdjust(N);P+=W+"</tr>"}Z++,Z>11&&(Z=0,te++),P+="</tbody></table>"+(X?"</div>"+(U[0]>0&&D===U[1]-1?"<div class='ui-datepicker-row-break'></div>":""):""),x+=P}y+=x}return y+=l,t._keyEvent=!1,y},_generateMonthYearHeader:function(t,e,i,s,n,o,a,r){var h,l,c,u,d,p,f,g,m=this._get(t,"changeMonth"),v=this._get(t,"changeYear"),_=this._get(t,"showMonthAfterYear"),b="<div class='ui-datepicker-title'>",y="";if(o||!m)y+="<span class='ui-datepicker-month'>"+a[e]+"</span>";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,y+="<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>",c=0;12>c;c++)(!h||c>=s.getMonth())&&(!l||n.getMonth()>=c)&&(y+="<option value='"+c+"'"+(c===e?" selected='selected'":"")+">"+r[c]+"</option>");y+="</select>"}if(_||(b+=y+(!o&&m&&v?"":" ")),!t.yearshtml)if(t.yearshtml="",o||!v)b+="<span class='ui-datepicker-year'>"+i+"</span>";else{for(u=this._get(t,"yearRange").split(":"),d=(new Date).getFullYear(),p=function(t){var e=t.match(/c[+\-].*/)?i+parseInt(t.substring(1),10):t.match(/[+\-].*/)?d+parseInt(t,10):parseInt(t,10);return isNaN(e)?d:e},f=p(u[0]),g=Math.max(f,p(u[1]||"")),f=s?Math.max(f,s.getFullYear()):f,g=n?Math.min(g,n.getFullYear()):g,t.yearshtml+="<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";g>=f;f++)t.yearshtml+="<option value='"+f+"'"+(f===i?" selected='selected'":"")+">"+f+"</option>";t.yearshtml+="</select>",b+=t.yearshtml,t.yearshtml=null}return b+=this._get(t,"yearSuffix"),_&&(b+=(!o&&m&&v?"":" ")+y),b+="</div>"},_adjustInstDate:function(t,e,i){var s=t.drawYear+("Y"===i?e:0),n=t.drawMonth+("M"===i?e:0),o=Math.min(t.selectedDay,this._getDaysInMonth(s,n))+("D"===i?e:0),a=this._restrictMinMax(t,this._daylightSavingAdjust(new Date(s,n,o)));t.selectedDay=a.getDate(),t.drawMonth=t.selectedMonth=a.getMonth(),t.drawYear=t.selectedYear=a.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(t)},_restrictMinMax:function(t,e){var i=this._getMinMaxDate(t,"min"),s=this._getMinMaxDate(t,"max"),n=i&&i>e?i:e;return s&&n>s?s:n},_notifyChange:function(t){var e=this._get(t,"onChangeMonthYear");e&&e.apply(t.input?t.input[0]:null,[t.selectedYear,t.selectedMonth+1,t])},_getNumberOfMonths:function(t){var e=this._get(t,"numberOfMonths");return null==e?[1,1]:"number"==typeof e?[1,e]:e},_getMinMaxDate:function(t,e){return this._determineDate(t,this._get(t,e+"Date"),null)},_getDaysInMonth:function(t,e){return 32-this._daylightSavingAdjust(new Date(t,e,32)).getDate()},_getFirstDayOfMonth:function(t,e){return new Date(t,e,1).getDay()},_canAdjustMonth:function(t,e,i,s){var n=this._getNumberOfMonths(t),o=this._daylightSavingAdjust(new Date(i,s+(0>e?e:n[0]*n[1]),1));return 0>e&&o.setDate(this._getDaysInMonth(o.getFullYear(),o.getMonth())),this._isInRange(t,o)},_isInRange:function(t,e){var i,s,n=this._getMinMaxDate(t,"min"),o=this._getMinMaxDate(t,"max"),a=null,r=null,h=this._get(t,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),a=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(a+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||e.getTime()>=n.getTime())&&(!o||e.getTime()<=o.getTime())&&(!a||e.getFullYear()>=a)&&(!r||r>=e.getFullYear())},_getFormatConfig:function(t){var e=this._get(t,"shortYearCutoff");return e="string"!=typeof e?e:(new Date).getFullYear()%100+parseInt(e,10),{shortYearCutoff:e,dayNamesShort:this._get(t,"dayNamesShort"),dayNames:this._get(t,"dayNames"),monthNamesShort:this._get(t,"monthNamesShort"),monthNames:this._get(t,"monthNames")}},_formatDate:function(t,e,i,s){e||(t.currentDay=t.selectedDay,t.currentMonth=t.selectedMonth,t.currentYear=t.selectedYear);var n=e?"object"==typeof e?e:this._daylightSavingAdjust(new Date(s,i,e)):this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return this.formatDate(this._get(t,"dateFormat"),n,this._getFormatConfig(t))}}),t.fn.datepicker=function(e){if(!this.length)return this;t.datepicker.initialized||(t(document).mousedown(t.datepicker._checkExternalClick),t.datepicker.initialized=!0),0===t("#"+t.datepicker._mainDivId).length&&t("body").append(t.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof e||"isDisabled"!==e&&"getDate"!==e&&"widget"!==e?"option"===e&&2===arguments.length&&"string"==typeof arguments[1]?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof e?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this].concat(i)):t.datepicker._attachDatepicker(this,e)}):t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i))},t.datepicker=new i,t.datepicker.initialized=!1,t.datepicker.uuid=(new Date).getTime(),t.datepicker.version="1.10.2",window["DP_jQuery_"+r]=t}(jQuery),function(t){var e={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},i={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0};t.widget("ui.dialog",{version:"1.10.2",options:{appendTo:"body",autoOpen:!0,buttons:[],closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(e){var i=t(this).css(e).offset().top;0>i&&t(this).css("top",e.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),this.options.title=this.options.title||this.originalTitle,this._createWrapper(),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&t.fn.draggable&&this._makeDraggable(),this.options.resizable&&t.fn.resizable&&this._makeResizable(),this._isOpen=!1},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var e=this.options.appendTo;return e&&(e.jquery||e.nodeType)?t(e):this.document.find(e||"body").eq(0)},_destroy:function(){var t,e=this.originalPosition;this._destroyOverlay(),this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach(),this.uiDialog.stop(!0,!0).remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),t=e.parent.children().eq(e.index),t.length&&t[0]!==this.element[0]?t.before(this.element):e.parent.append(this.element)},widget:function(){return this.uiDialog},disable:t.noop,enable:t.noop,close:function(e){var i=this;this._isOpen&&this._trigger("beforeClose",e)!==!1&&(this._isOpen=!1,this._destroyOverlay(),this.opener.filter(":focusable").focus().length||t(this.document[0].activeElement).blur(),this._hide(this.uiDialog,this.options.hide,function(){i._trigger("close",e)}))},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(t,e){var i=!!this.uiDialog.nextAll(":visible").insertBefore(this.uiDialog).length;return i&&!e&&this._trigger("focus",t),i},open:function(){var e=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),undefined):(this._isOpen=!0,this.opener=t(this.document[0].activeElement),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this._show(this.uiDialog,this.options.show,function(){e._focusTabbable(),e._trigger("focus")}),this._trigger("open"),undefined)},_focusTabbable:function(){var t=this.element.find("[autofocus]");t.length||(t=this.element.find(":tabbable")),t.length||(t=this.uiDialogButtonPane.find(":tabbable")),t.length||(t=this.uiDialogTitlebarClose.filter(":tabbable")),t.length||(t=this.uiDialog),t.eq(0).focus()},_keepFocus:function(e){function i(){var e=this.document[0].activeElement,i=this.uiDialog[0]===e||t.contains(this.uiDialog[0],e);i||this._focusTabbable()}e.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=t("<div>").addClass("ui-dialog ui-widget ui-widget-content ui-corner-all ui-front "+this.options.dialogClass).hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._on(this.uiDialog,{keydown:function(e){if(this.options.closeOnEscape&&!e.isDefaultPrevented()&&e.keyCode&&e.keyCode===t.ui.keyCode.ESCAPE)return e.preventDefault(),this.close(e),undefined;if(e.keyCode===t.ui.keyCode.TAB){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");e.target!==n[0]&&e.target!==this.uiDialog[0]||e.shiftKey?e.target!==s[0]&&e.target!==this.uiDialog[0]||!e.shiftKey||(n.focus(1),e.preventDefault()):(s.focus(1),e.preventDefault())}},mousedown:function(t){this._moveToTop(t)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var e;this.uiDialogTitlebar=t("<div>").addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(this.uiDialog),this._on(this.uiDialogTitlebar,{mousedown:function(e){t(e.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.focus()}}),this.uiDialogTitlebarClose=t("<button></button>").button({label:this.options.closeText,icons:{primary:"ui-icon-closethick"},text:!1}).addClass("ui-dialog-titlebar-close").appendTo(this.uiDialogTitlebar),this._on(this.uiDialogTitlebarClose,{click:function(t){t.preventDefault(),this.close(t)}}),e=t("<span>").uniqueId().addClass("ui-dialog-title").prependTo(this.uiDialogTitlebar),this._title(e),this.uiDialog.attr({"aria-labelledby":e.attr("id")})},_title:function(t){this.options.title||t.html(" "),t.text(this.options.title)},_createButtonPane:function(){this.uiDialogButtonPane=t("<div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),this.uiButtonSet=t("<div>").addClass("ui-dialog-buttonset").appendTo(this.uiDialogButtonPane),this._createButtons()},_createButtons:function(){var e=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),t.isEmptyObject(i)||t.isArray(i)&&!i.length?(this.uiDialog.removeClass("ui-dialog-buttons"),undefined):(t.each(i,function(i,s){var n,o;s=t.isFunction(s)?{click:s,text:i}:s,s=t.extend({type:"button"},s),n=s.click,s.click=function(){n.apply(e.element[0],arguments)},o={icons:s.icons,text:s.showText},delete s.icons,delete s.showText,t("<button></button>",s).button(o).appendTo(e.uiButtonSet)}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),undefined)},_makeDraggable:function(){function e(t){return{position:t.position,offset:t.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){t(this).addClass("ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,e(n))},drag:function(t,s){i._trigger("drag",t,e(s))},stop:function(n,o){s.position=[o.position.left-i.document.scrollLeft(),o.position.top-i.document.scrollTop()],t(this).removeClass("ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,e(o)) +}})},_makeResizable:function(){function e(t){return{originalPosition:t.originalPosition,originalSize:t.originalSize,position:t.position,size:t.size}}var i=this,s=this.options,n=s.resizable,o=this.uiDialog.css("position"),a="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:a,start:function(s,n){t(this).addClass("ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,e(n))},resize:function(t,s){i._trigger("resize",t,e(s))},stop:function(n,o){s.height=t(this).height(),s.width=t(this).width(),t(this).removeClass("ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,e(o))}}).css("position",o)},_minHeight:function(){var t=this.options;return"auto"===t.height?t.minHeight:Math.min(t.minHeight,t.height)},_position:function(){var t=this.uiDialog.is(":visible");t||this.uiDialog.show(),this.uiDialog.position(this.options.position),t||this.uiDialog.hide()},_setOptions:function(s){var n=this,o=!1,a={};t.each(s,function(t,s){n._setOption(t,s),t in e&&(o=!0),t in i&&(a[t]=s)}),o&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",a)},_setOption:function(t,e){var i,s,n=this.uiDialog;"dialogClass"===t&&n.removeClass(this.options.dialogClass).addClass(e),"disabled"!==t&&(this._super(t,e),"appendTo"===t&&this.uiDialog.appendTo(this._appendTo()),"buttons"===t&&this._createButtons(),"closeText"===t&&this.uiDialogTitlebarClose.button({label:""+e}),"draggable"===t&&(i=n.is(":data(ui-draggable)"),i&&!e&&n.draggable("destroy"),!i&&e&&this._makeDraggable()),"position"===t&&this._position(),"resizable"===t&&(s=n.is(":data(ui-resizable)"),s&&!e&&n.resizable("destroy"),s&&"string"==typeof e&&n.resizable("option","handles",e),s||e===!1||this._makeResizable()),"title"===t&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var t,e,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),t=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),e=Math.max(0,s.minHeight-t),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-t):"none","auto"===s.height?this.element.css({minHeight:e,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-t)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var e=t(this);return t("<div>").css({position:"absolute",width:e.outerWidth(),height:e.outerHeight()}).appendTo(e.parent()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(e){return t(e.target).closest(".ui-dialog").length?!0:!!t(e.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var e=this,i=this.widgetFullName;t.ui.dialog.overlayInstances||this._delay(function(){t.ui.dialog.overlayInstances&&this.document.bind("focusin.dialog",function(s){e._allowInteraction(s)||(s.preventDefault(),t(".ui-dialog:visible:last .ui-dialog-content").data(i)._focusTabbable())})}),this.overlay=t("<div>").addClass("ui-widget-overlay ui-front").appendTo(this._appendTo()),this._on(this.overlay,{mousedown:"_keepFocus"}),t.ui.dialog.overlayInstances++}},_destroyOverlay:function(){this.options.modal&&this.overlay&&(t.ui.dialog.overlayInstances--,t.ui.dialog.overlayInstances||this.document.unbind("focusin.dialog"),this.overlay.remove(),this.overlay=null)}}),t.ui.dialog.overlayInstances=0,t.uiBackCompat!==!1&&t.widget("ui.dialog",t.ui.dialog,{_position:function(){var e,i=this.options.position,s=[],n=[0,0];i?(("string"==typeof i||"object"==typeof i&&"0"in i)&&(s=i.split?i.split(" "):[i[0],i[1]],1===s.length&&(s[1]=s[0]),t.each(["left","top"],function(t,e){+s[t]===s[t]&&(n[t]=s[t],s[t]=e)}),i={my:s[0]+(0>n[0]?n[0]:"+"+n[0])+" "+s[1]+(0>n[1]?n[1]:"+"+n[1]),at:s.join(" ")}),i=t.extend({},t.ui.dialog.prototype.options.position,i)):i=t.ui.dialog.prototype.options.position,e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.position(i),e||this.uiDialog.hide()}})}(jQuery),function(t){var e=/up|down|vertical/,i=/up|left|vertical|horizontal/;t.effects.effect.blind=function(s,n){var o,a,r,h=t(this),l=["position","top","bottom","left","right","height","width"],c=t.effects.setMode(h,s.mode||"hide"),u=s.direction||"up",d=e.test(u),p=d?"height":"width",f=d?"top":"left",g=i.test(u),m={},v="show"===c;h.parent().is(".ui-effects-wrapper")?t.effects.save(h.parent(),l):t.effects.save(h,l),h.show(),o=t.effects.createWrapper(h).css({overflow:"hidden"}),a=o[p](),r=parseFloat(o.css(f))||0,m[p]=v?a:0,g||(h.css(d?"bottom":"right",0).css(d?"top":"left","auto").css({position:"absolute"}),m[f]=v?r:a+r),v&&(o.css(p,0),g||o.css(f,r+a)),o.animate(m,{duration:s.duration,easing:s.easing,queue:!1,complete:function(){"hide"===c&&h.hide(),t.effects.restore(h,l),t.effects.removeWrapper(h),n()}})}}(jQuery),function(t){t.effects.effect.bounce=function(e,i){var s,n,o,a=t(this),r=["position","top","bottom","left","right","height","width"],h=t.effects.setMode(a,e.mode||"effect"),l="hide"===h,c="show"===h,u=e.direction||"up",d=e.distance,p=e.times||5,f=2*p+(c||l?1:0),g=e.duration/f,m=e.easing,v="up"===u||"down"===u?"top":"left",_="up"===u||"left"===u,b=a.queue(),y=b.length;for((c||l)&&r.push("opacity"),t.effects.save(a,r),a.show(),t.effects.createWrapper(a),d||(d=a["top"===v?"outerHeight":"outerWidth"]()/3),c&&(o={opacity:1},o[v]=0,a.css("opacity",0).css(v,_?2*-d:2*d).animate(o,g,m)),l&&(d/=Math.pow(2,p-1)),o={},o[v]=0,s=0;p>s;s++)n={},n[v]=(_?"-=":"+=")+d,a.animate(n,g,m).animate(o,g,m),d=l?2*d:d/2;l&&(n={opacity:0},n[v]=(_?"-=":"+=")+d,a.animate(n,g,m)),a.queue(function(){l&&a.hide(),t.effects.restore(a,r),t.effects.removeWrapper(a),i()}),y>1&&b.splice.apply(b,[1,0].concat(b.splice(y,f+1))),a.dequeue()}}(jQuery),function(t){t.effects.effect.clip=function(e,i){var s,n,o,a=t(this),r=["position","top","bottom","left","right","height","width"],h=t.effects.setMode(a,e.mode||"hide"),l="show"===h,c=e.direction||"vertical",u="vertical"===c,d=u?"height":"width",p=u?"top":"left",f={};t.effects.save(a,r),a.show(),s=t.effects.createWrapper(a).css({overflow:"hidden"}),n="IMG"===a[0].tagName?s:a,o=n[d](),l&&(n.css(d,0),n.css(p,o/2)),f[d]=l?o:0,f[p]=l?0:o/2,n.animate(f,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){l||a.hide(),t.effects.restore(a,r),t.effects.removeWrapper(a),i()}})}}(jQuery),function(t){t.effects.effect.drop=function(e,i){var s,n=t(this),o=["position","top","bottom","left","right","opacity","height","width"],a=t.effects.setMode(n,e.mode||"hide"),r="show"===a,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h?"pos":"neg",u={opacity:r?1:0};t.effects.save(n,o),n.show(),t.effects.createWrapper(n),s=e.distance||n["top"===l?"outerHeight":"outerWidth"](!0)/2,r&&n.css("opacity",0).css(l,"pos"===c?-s:s),u[l]=(r?"pos"===c?"+=":"-=":"pos"===c?"-=":"+=")+s,n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===a&&n.hide(),t.effects.restore(n,o),t.effects.removeWrapper(n),i()}})}}(jQuery),function(t){t.effects.effect.explode=function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),g||p.hide(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=t.effects.setMode(p,e.mode||"hide"),g="show"===f,m=p.show().css("visibility","hidden").offset(),v=Math.ceil(p.outerWidth()/d),_=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*_,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*v,l=a-(d-1)/2,p.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-a*v,top:-o*_}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:v,height:_,left:r+(g?l*v:0),top:h+(g?c*_:0),opacity:g?0:1}).animate({left:r+(g?0:l*v),top:h+(g?0:c*_),opacity:g?1:0},e.duration||500,e.easing,s)}}(jQuery),function(t){t.effects.effect.fade=function(e,i){var s=t(this),n=t.effects.setMode(s,e.mode||"toggle");s.animate({opacity:n},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}}(jQuery),function(t){t.effects.effect.fold=function(e,i){var s,n,o=t(this),a=["position","top","bottom","left","right","height","width"],r=t.effects.setMode(o,e.mode||"hide"),h="show"===r,l="hide"===r,c=e.size||15,u=/([0-9]+)%/.exec(c),d=!!e.horizFirst,p=h!==d,f=p?["width","height"]:["height","width"],g=e.duration/2,m={},v={};t.effects.save(o,a),o.show(),s=t.effects.createWrapper(o).css({overflow:"hidden"}),n=p?[s.width(),s.height()]:[s.height(),s.width()],u&&(c=parseInt(u[1],10)/100*n[l?0:1]),h&&s.css(d?{height:0,width:c}:{height:c,width:0}),m[f[0]]=h?n[0]:c,v[f[1]]=h?n[1]:0,s.animate(m,g,e.easing).animate(v,g,e.easing,function(){l&&o.hide(),t.effects.restore(o,a),t.effects.removeWrapper(o),i()})}}(jQuery),function(t){t.effects.effect.highlight=function(e,i){var s=t(this),n=["backgroundImage","backgroundColor","opacity"],o=t.effects.setMode(s,e.mode||"show"),a={backgroundColor:s.css("backgroundColor")};"hide"===o&&(a.opacity=0),t.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(a,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===o&&s.hide(),t.effects.restore(s,n),i()}})}}(jQuery),function(t){t.effects.effect.pulsate=function(e,i){var s,n=t(this),o=t.effects.setMode(n,e.mode||"show"),a="show"===o,r="hide"===o,h=a||"hide"===o,l=2*(e.times||5)+(h?1:0),c=e.duration/l,u=0,d=n.queue(),p=d.length;for((a||!n.is(":visible"))&&(n.css("opacity",0).show(),u=1),s=1;l>s;s++)n.animate({opacity:u},c,e.easing),u=1-u;n.animate({opacity:u},c,e.easing),n.queue(function(){r&&n.hide(),i()}),p>1&&d.splice.apply(d,[1,0].concat(d.splice(p,l+1))),n.dequeue()}}(jQuery),function(t){t.effects.effect.puff=function(e,i){var s=t(this),n=t.effects.setMode(s,e.mode||"hide"),o="hide"===n,a=parseInt(e.percent,10)||150,r=a/100,h={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()};t.extend(e,{effect:"scale",queue:!1,fade:!0,mode:n,complete:i,percent:o?a:100,from:o?h:{height:h.height*r,width:h.width*r,outerHeight:h.outerHeight*r,outerWidth:h.outerWidth*r}}),s.effect(e)},t.effects.effect.scale=function(e,i){var s=t(this),n=t.extend(!0,{},e),o=t.effects.setMode(s,e.mode||"effect"),a=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"hide"===o?0:100),r=e.direction||"both",h=e.origin,l={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()},c={y:"horizontal"!==r?a/100:1,x:"vertical"!==r?a/100:1};n.effect="size",n.queue=!1,n.complete=i,"effect"!==o&&(n.origin=h||["middle","center"],n.restore=!0),n.from=e.from||("show"===o?{height:0,width:0,outerHeight:0,outerWidth:0}:l),n.to={height:l.height*c.y,width:l.width*c.x,outerHeight:l.outerHeight*c.y,outerWidth:l.outerWidth*c.x},n.fade&&("show"===o&&(n.from.opacity=0,n.to.opacity=1),"hide"===o&&(n.from.opacity=1,n.to.opacity=0)),s.effect(n)},t.effects.effect.size=function(e,i){var s,n,o,a=t(this),r=["position","top","bottom","left","right","width","height","overflow","opacity"],h=["position","top","bottom","left","right","overflow","opacity"],l=["width","height","overflow"],c=["fontSize"],u=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],d=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=t.effects.setMode(a,e.mode||"effect"),f=e.restore||"effect"!==p,g=e.scale||"both",m=e.origin||["middle","center"],v=a.css("position"),_=f?r:h,b={height:0,width:0,outerHeight:0,outerWidth:0};"show"===p&&a.show(),s={height:a.height(),width:a.width(),outerHeight:a.outerHeight(),outerWidth:a.outerWidth()},"toggle"===e.mode&&"show"===p?(a.from=e.to||b,a.to=e.from||s):(a.from=e.from||("show"===p?b:s),a.to=e.to||("hide"===p?b:s)),o={from:{y:a.from.height/s.height,x:a.from.width/s.width},to:{y:a.to.height/s.height,x:a.to.width/s.width}},("box"===g||"both"===g)&&(o.from.y!==o.to.y&&(_=_.concat(u),a.from=t.effects.setTransition(a,u,o.from.y,a.from),a.to=t.effects.setTransition(a,u,o.to.y,a.to)),o.from.x!==o.to.x&&(_=_.concat(d),a.from=t.effects.setTransition(a,d,o.from.x,a.from),a.to=t.effects.setTransition(a,d,o.to.x,a.to))),("content"===g||"both"===g)&&o.from.y!==o.to.y&&(_=_.concat(c).concat(l),a.from=t.effects.setTransition(a,c,o.from.y,a.from),a.to=t.effects.setTransition(a,c,o.to.y,a.to)),t.effects.save(a,_),a.show(),t.effects.createWrapper(a),a.css("overflow","hidden").css(a.from),m&&(n=t.effects.getBaseline(m,s),a.from.top=(s.outerHeight-a.outerHeight())*n.y,a.from.left=(s.outerWidth-a.outerWidth())*n.x,a.to.top=(s.outerHeight-a.to.outerHeight)*n.y,a.to.left=(s.outerWidth-a.to.outerWidth)*n.x),a.css(a.from),("content"===g||"both"===g)&&(u=u.concat(["marginTop","marginBottom"]).concat(c),d=d.concat(["marginLeft","marginRight"]),l=r.concat(u).concat(d),a.find("*[width]").each(function(){var i=t(this),s={height:i.height(),width:i.width(),outerHeight:i.outerHeight(),outerWidth:i.outerWidth()};f&&t.effects.save(i,l),i.from={height:s.height*o.from.y,width:s.width*o.from.x,outerHeight:s.outerHeight*o.from.y,outerWidth:s.outerWidth*o.from.x},i.to={height:s.height*o.to.y,width:s.width*o.to.x,outerHeight:s.height*o.to.y,outerWidth:s.width*o.to.x},o.from.y!==o.to.y&&(i.from=t.effects.setTransition(i,u,o.from.y,i.from),i.to=t.effects.setTransition(i,u,o.to.y,i.to)),o.from.x!==o.to.x&&(i.from=t.effects.setTransition(i,d,o.from.x,i.from),i.to=t.effects.setTransition(i,d,o.to.x,i.to)),i.css(i.from),i.animate(i.to,e.duration,e.easing,function(){f&&t.effects.restore(i,l)})})),a.animate(a.to,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){0===a.to.opacity&&a.css("opacity",a.from.opacity),"hide"===p&&a.hide(),t.effects.restore(a,_),f||("static"===v?a.css({position:"relative",top:a.to.top,left:a.to.left}):t.each(["top","left"],function(t,e){a.css(e,function(e,i){var s=parseInt(i,10),n=t?a.to.left:a.to.top;return"auto"===i?n+"px":s+n+"px"})})),t.effects.removeWrapper(a),i()}})}}(jQuery),function(t){t.effects.effect.shake=function(e,i){var s,n=t(this),o=["position","top","bottom","left","right","height","width"],a=t.effects.setMode(n,e.mode||"effect"),r=e.direction||"left",h=e.distance||20,l=e.times||3,c=2*l+1,u=Math.round(e.duration/c),d="up"===r||"down"===r?"top":"left",p="up"===r||"left"===r,f={},g={},m={},v=n.queue(),_=v.length;for(t.effects.save(n,o),n.show(),t.effects.createWrapper(n),f[d]=(p?"-=":"+=")+h,g[d]=(p?"+=":"-=")+2*h,m[d]=(p?"-=":"+=")+2*h,n.animate(f,u,e.easing),s=1;l>s;s++)n.animate(g,u,e.easing).animate(m,u,e.easing);n.animate(g,u,e.easing).animate(f,u/2,e.easing).queue(function(){"hide"===a&&n.hide(),t.effects.restore(n,o),t.effects.removeWrapper(n),i()}),_>1&&v.splice.apply(v,[1,0].concat(v.splice(_,c+1))),n.dequeue()}}(jQuery),function(t){t.effects.effect.slide=function(e,i){var s,n=t(this),o=["position","top","bottom","left","right","width","height"],a=t.effects.setMode(n,e.mode||"show"),r="show"===a,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u={};t.effects.save(n,o),n.show(),s=e.distance||n["top"===l?"outerHeight":"outerWidth"](!0),t.effects.createWrapper(n).css({overflow:"hidden"}),r&&n.css(l,c?isNaN(s)?"-"+s:-s:s),u[l]=(r?c?"+=":"-=":c?"-=":"+=")+s,n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===a&&n.hide(),t.effects.restore(n,o),t.effects.removeWrapper(n),i()}})}}(jQuery),function(t){t.effects.effect.transfer=function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("<div class='ui-effects-transfer'></div>").appendTo(document.body).addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),i()})}}(jQuery),function(t){t.widget("ui.menu",{version:"1.10.2",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,t.proxy(function(t){this.options.disabled&&t.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(t){t.preventDefault()},"click .ui-state-disabled > a":function(t){t.preventDefault()},"click .ui-menu-item:has(a)":function(e){var i=t(e.target).closest(".ui-menu-item");!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.mouseHandled=!0,this.select(e),i.has(".ui-menu").length?this.expand(e):this.element.is(":focus")||(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){var i=t(e.currentTarget);i.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(e,i)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.children(".ui-menu-item").eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){t.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){t(e.target).closest(".ui-menu").length||this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var e=t(this);e.data("ui-menu-submenu-carat")&&e.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(e){function i(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var s,n,o,a,r,h=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:h=!1,n=this.previousFilter||"",o=String.fromCharCode(e.keyCode),a=!1,clearTimeout(this.filterTimer),o===n?a=!0:o=n+o,r=RegExp("^"+i(o),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())}),s=a&&-1!==s.index(this.active.next())?this.active.nextAll(".ui-menu-item"):s,s.length||(o=String.fromCharCode(e.keyCode),r=RegExp("^"+i(o),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())})),s.length?(this.focus(e,s),s.length>1?(this.previousFilter=o,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}h&&e.preventDefault()},_activate:function(t){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i=this.options.icons.submenu,s=this.element.find(this.options.menus);s.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),s=e.prev("a"),n=t("<span>").addClass("ui-menu-icon ui-icon "+i).data("ui-menu-submenu-carat",!0);s.attr("aria-haspopup","true").prepend(n),e.attr("aria-labelledby",s.attr("id"))}),e=s.add(this.element),e.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),e.children(":not(.ui-menu-item)").each(function(){var e=t(this);/[^\-\u2014\u2013\s]/.test(e.text())||e.addClass("ui-widget-content ui-menu-divider")}),e.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){"icons"===t&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(e.submenu),this._super(t,e)},focus:function(t,e){var i,s;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.height(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",t,{item:this.active}))},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.children(".ui-menu-item")[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())),undefined):(this.next(e),undefined)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item").first())),undefined):(this.next(e),undefined)},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(e){this.active=this.active||t(e.target).closest(".ui-menu-item");var i={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(e,!0),this._trigger("select",e,i)}})}(jQuery),function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var o,a=Math.max,r=Math.abs,h=Math.round,l=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(o!==e)return o;var i,s,n=t("<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),a=n.children()[0];return t("body").append(n),i=a.offsetWidth,n.css("overflow","scroll"),s=a.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),o=i-s},getScrollInfo:function(e){var i=e.isWindow?"":e.element.css("overflow-x"),s=e.isWindow?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth,o="scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight;return{width:o?t.position.scrollbarWidth():0,height:n?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]);return{element:i,isWindow:s,offset:i.offset()||{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:s?i.width():i.outerWidth(),height:s?i.height():i.outerHeight()}}},t.fn.position=function(e){if(!e||!e.of)return f.apply(this,arguments);e=t.extend({},e);var o,p,g,m,v,_,b=t(e.of),y=t.position.getWithinInfo(e.within),w=t.position.getScrollInfo(y),k=(e.collision||"flip").split(" "),x={};return _=n(b),b[0].preventDefault&&(e.at="left top"),p=_.width,g=_.height,m=_.offset,v=t.extend({},m),t.each(["my","at"],function(){var t,i,s=(e[this]||"").split(" ");1===s.length&&(s=l.test(s[0])?s.concat(["center"]):c.test(s[0])?["center"].concat(s):["center","center"]),s[0]=l.test(s[0])?s[0]:"center",s[1]=c.test(s[1])?s[1]:"center",t=u.exec(s[0]),i=u.exec(s[1]),x[this]=[t?t[0]:0,i?i[0]:0],e[this]=[d.exec(s[0])[0],d.exec(s[1])[0]]}),1===k.length&&(k[1]=k[0]),"right"===e.at[0]?v.left+=p:"center"===e.at[0]&&(v.left+=p/2),"bottom"===e.at[1]?v.top+=g:"center"===e.at[1]&&(v.top+=g/2),o=i(x.at,p,g),v.left+=o[0],v.top+=o[1],this.each(function(){var n,l,c=t(this),u=c.outerWidth(),d=c.outerHeight(),f=s(this,"marginLeft"),_=s(this,"marginTop"),D=u+f+s(this,"marginRight")+w.width,C=d+_+s(this,"marginBottom")+w.height,I=t.extend({},v),P=i(x.my,c.outerWidth(),c.outerHeight());"right"===e.my[0]?I.left-=u:"center"===e.my[0]&&(I.left-=u/2),"bottom"===e.my[1]?I.top-=d:"center"===e.my[1]&&(I.top-=d/2),I.left+=P[0],I.top+=P[1],t.support.offsetFractions||(I.left=h(I.left),I.top=h(I.top)),n={marginLeft:f,marginTop:_},t.each(["left","top"],function(i,s){t.ui.position[k[i]]&&t.ui.position[k[i]][s](I,{targetWidth:p,targetHeight:g,elemWidth:u,elemHeight:d,collisionPosition:n,collisionWidth:D,collisionHeight:C,offset:[o[0]+P[0],o[1]+P[1]],my:e.my,at:e.at,within:y,elem:c})}),e.using&&(l=function(t){var i=m.left-I.left,s=i+p-u,n=m.top-I.top,o=n+g-d,h={target:{element:b,left:m.left,top:m.top,width:p,height:g},element:{element:c,left:I.left,top:I.top,width:u,height:d},horizontal:0>s?"left":i>0?"right":"center",vertical:0>o?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(h.horizontal="center"),d>g&&g>r(n+o)&&(h.vertical="middle"),h.important=a(r(i),r(s))>a(r(n),r(o))?"horizontal":"vertical",e.using.call(this,t,h)}),c.offset(t.extend(I,{using:l}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,o=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-o-n;e.collisionWidth>o?h>0&&0>=l?(i=t.left+h+e.collisionWidth-o-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+o-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=a(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,o=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-o-n;e.collisionHeight>o?h>0&&0>=l?(i=t.top+h+e.collisionHeight-o-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+o-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=a(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,a=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-a-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-a-o,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,a=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-a-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-a-o,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,o,a=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(a?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},a&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(o in s)e.style[o]=s[o];e.appendChild(r),i=a||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()}(jQuery),function(t,e){t.widget("ui.progressbar",{version:"1.10.2",options:{max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min}),this.valueDiv=t("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this._refreshValue() +},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(t){return t===e?this.options.value:(this.options.value=this._constrainedValue(t),this._refreshValue(),e)},_constrainedValue:function(t){return t===e&&(t=this.options.value),this.indeterminate=t===!1,"number"!=typeof t&&(t=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,t))},_setOptions:function(t){var e=t.value;delete t.value,this._super(t),this.options.value=this._constrainedValue(e),this._refreshValue()},_setOption:function(t,e){"max"===t&&(e=Math.max(this.min,e)),this._super(t,e)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var e=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||e>this.min).toggleClass("ui-corner-right",e===this.options.max).width(i.toFixed(0)+"%"),this.element.toggleClass("ui-progressbar-indeterminate",this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=t("<div class='ui-progressbar-overlay'></div>").appendTo(this.valueDiv))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":e}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),e===this.options.max&&this._trigger("complete")}})}(jQuery),function(t){var e=5;t.widget("ui.slider",t.ui.mouse,{version:"1.10.2",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},_create:function(){this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"),this._refresh(),this._setOption("disabled",this.options.disabled),this._animateOff=!1},_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var e,i,s=this.options,n=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),o="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",a=[];for(i=s.values&&s.values.length||1,n.length>i&&(n.slice(i).remove(),n=n.slice(0,i)),e=n.length;i>e;e++)a.push(o);this.handles=n.add(t(a.join("")).appendTo(this.element)),this.handle=this.handles.eq(0),this.handles.each(function(e){t(this).data("ui-slider-handle-index",e)})},_createRange:function(){var e=this.options,i="";e.range?(e.range===!0&&(e.values?e.values.length&&2!==e.values.length?e.values=[e.values[0],e.values[0]]:t.isArray(e.values)&&(e.values=e.values.slice(0)):e.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?this.range.removeClass("ui-slider-range-min ui-slider-range-max").css({left:"",bottom:""}):(this.range=t("<div></div>").appendTo(this.element),i="ui-slider-range ui-widget-header ui-corner-all"),this.range.addClass(i+("min"===e.range||"max"===e.range?" ui-slider-range-"+e.range:""))):this.range=t([])},_setupEvents:function(){var t=this.handles.add(this.range).filter("a");this._off(t),this._on(t,this._handleEvents),this._hoverable(t),this._focusable(t)},_destroy:function(){this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-widget ui-widget-content ui-corner-all"),this._mouseDestroy()},_mouseCapture:function(e){var i,s,n,o,a,r,h,l,c=this,u=this.options;return u.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:e.pageX,y:e.pageY},s=this._normValueFromMouse(i),n=this._valueMax()-this._valueMin()+1,this.handles.each(function(e){var i=Math.abs(s-c.values(e));(n>i||n===i&&(e===c._lastChangedValue||c.values(e)===u.min))&&(n=i,o=t(this),a=e)}),r=this._start(e,a),r===!1?!1:(this._mouseSliding=!0,this._handleIndex=a,o.addClass("ui-state-active").focus(),h=o.offset(),l=!t(e.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:e.pageX-h.left-o.width()/2,top:e.pageY-h.top-o.height()/2-(parseInt(o.css("borderTopWidth"),10)||0)-(parseInt(o.css("borderBottomWidth"),10)||0)+(parseInt(o.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(e,a,s),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(t){var e={x:t.pageX,y:t.pageY},i=this._normValueFromMouse(e);return this._slide(t,this._handleIndex,i),!1},_mouseStop:function(t){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(t,this._handleIndex),this._change(t,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(t){var e,i,s,n,o;return"horizontal"===this.orientation?(e=this.elementSize.width,i=t.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(e=this.elementSize.height,i=t.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),s=i/e,s>1&&(s=1),0>s&&(s=0),"vertical"===this.orientation&&(s=1-s),n=this._valueMax()-this._valueMin(),o=this._valueMin()+s*n,this._trimAlignValue(o)},_start:function(t,e){var i={handle:this.handles[e],value:this.value()};return this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._trigger("start",t,i)},_slide:function(t,e,i){var s,n,o;this.options.values&&this.options.values.length?(s=this.values(e?0:1),2===this.options.values.length&&this.options.range===!0&&(0===e&&i>s||1===e&&s>i)&&(i=s),i!==this.values(e)&&(n=this.values(),n[e]=i,o=this._trigger("slide",t,{handle:this.handles[e],value:i,values:n}),s=this.values(e?0:1),o!==!1&&this.values(e,i,!0))):i!==this.value()&&(o=this._trigger("slide",t,{handle:this.handles[e],value:i}),o!==!1&&this.value(i))},_stop:function(t,e){var i={handle:this.handles[e],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._trigger("stop",t,i)},_change:function(t,e){if(!this._keySliding&&!this._mouseSliding){var i={handle:this.handles[e],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._lastChangedValue=e,this._trigger("change",t,i)}},value:function(t){return arguments.length?(this.options.value=this._trimAlignValue(t),this._refreshValue(),this._change(null,0),undefined):this._value()},values:function(e,i){var s,n,o;if(arguments.length>1)return this.options.values[e]=this._trimAlignValue(i),this._refreshValue(),this._change(null,e),undefined;if(!arguments.length)return this._values();if(!t.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(e):this.value();for(s=this.options.values,n=arguments[0],o=0;s.length>o;o+=1)s[o]=this._trimAlignValue(n[o]),this._change(null,o);this._refreshValue()},_setOption:function(e,i){var s,n=0;switch("range"===e&&this.options.range===!0&&("min"===i?(this.options.value=this._values(0),this.options.values=null):"max"===i&&(this.options.value=this._values(this.options.values.length-1),this.options.values=null)),t.isArray(this.options.values)&&(n=this.options.values.length),t.Widget.prototype._setOption.apply(this,arguments),e){case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue();break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":for(this._animateOff=!0,this._refreshValue(),s=0;n>s;s+=1)this._change(null,s);this._animateOff=!1;break;case"min":case"max":this._animateOff=!0,this._refreshValue(),this._animateOff=!1;break;case"range":this._animateOff=!0,this._refresh(),this._animateOff=!1}},_value:function(){var t=this.options.value;return t=this._trimAlignValue(t)},_values:function(t){var e,i,s;if(arguments.length)return e=this.options.values[t],e=this._trimAlignValue(e);if(this.options.values&&this.options.values.length){for(i=this.options.values.slice(),s=0;i.length>s;s+=1)i[s]=this._trimAlignValue(i[s]);return i}return[]},_trimAlignValue:function(t){if(this._valueMin()>=t)return this._valueMin();if(t>=this._valueMax())return this._valueMax();var e=this.options.step>0?this.options.step:1,i=(t-this._valueMin())%e,s=t-i;return 2*Math.abs(i)>=e&&(s+=i>0?e:-e),parseFloat(s.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var e,i,s,n,o,a=this.options.range,r=this.options,h=this,l=this._animateOff?!1:r.animate,c={};this.options.values&&this.options.values.length?this.handles.each(function(s){i=100*((h.values(s)-h._valueMin())/(h._valueMax()-h._valueMin())),c["horizontal"===h.orientation?"left":"bottom"]=i+"%",t(this).stop(1,1)[l?"animate":"css"](c,r.animate),h.options.range===!0&&("horizontal"===h.orientation?(0===s&&h.range.stop(1,1)[l?"animate":"css"]({left:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({width:i-e+"%"},{queue:!1,duration:r.animate})):(0===s&&h.range.stop(1,1)[l?"animate":"css"]({bottom:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({height:i-e+"%"},{queue:!1,duration:r.animate}))),e=i}):(s=this.value(),n=this._valueMin(),o=this._valueMax(),i=o!==n?100*((s-n)/(o-n)):0,c["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[l?"animate":"css"](c,r.animate),"min"===a&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:i+"%"},r.animate),"max"===a&&"horizontal"===this.orientation&&this.range[l?"animate":"css"]({width:100-i+"%"},{queue:!1,duration:r.animate}),"min"===a&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:i+"%"},r.animate),"max"===a&&"vertical"===this.orientation&&this.range[l?"animate":"css"]({height:100-i+"%"},{queue:!1,duration:r.animate}))},_handleEvents:{keydown:function(i){var s,n,o,a,r=t(i.target).data("ui-slider-handle-index");switch(i.keyCode){case t.ui.keyCode.HOME:case t.ui.keyCode.END:case t.ui.keyCode.PAGE_UP:case t.ui.keyCode.PAGE_DOWN:case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(i.preventDefault(),!this._keySliding&&(this._keySliding=!0,t(i.target).addClass("ui-state-active"),s=this._start(i,r),s===!1))return}switch(a=this.options.step,n=o=this.options.values&&this.options.values.length?this.values(r):this.value(),i.keyCode){case t.ui.keyCode.HOME:o=this._valueMin();break;case t.ui.keyCode.END:o=this._valueMax();break;case t.ui.keyCode.PAGE_UP:o=this._trimAlignValue(n+(this._valueMax()-this._valueMin())/e);break;case t.ui.keyCode.PAGE_DOWN:o=this._trimAlignValue(n-(this._valueMax()-this._valueMin())/e);break;case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:if(n===this._valueMax())return;o=this._trimAlignValue(n+a);break;case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(n===this._valueMin())return;o=this._trimAlignValue(n-a)}this._slide(i,r,o)},click:function(t){t.preventDefault()},keyup:function(e){var i=t(e.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(e,i),this._change(e,i),t(e.target).removeClass("ui-state-active"))}}})}(jQuery),function(t){function e(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.widget("ui.spinner",{version:"1.10.2",defaultElement:"<input>",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var e={},i=this.element;return t.each(["min","max","step"],function(t,s){var n=i.attr(s);void 0!==n&&n.length&&(e[s]=n)}),e},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t),void 0)},mousewheel:function(t,e){if(e){if(!this.spinning&&!this._start(t))return!1;this._spin((e>0?1:-1)*this.options.step,t),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(t)},100),t.preventDefault()}},"mousedown .ui-spinner-button":function(e){function i(){var t=this.element[0]===this.document[0].activeElement;t||(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),e.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(e)!==!1&&this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(e){return t(e.currentTarget).hasClass("ui-state-active")?this._start(e)===!1?!1:(this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var t=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=t.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(.5*t.height())&&t.height()>0&&t.height(t.height()),this.options.disabled&&this.disable()},_keydown:function(e){var i=this.options,s=t.ui.keyCode;switch(e.keyCode){case s.UP:return this._repeat(null,1,e),!0;case s.DOWN:return this._repeat(null,-1,e),!0;case s.PAGE_UP:return this._repeat(null,i.page,e),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,e),!0}return!1},_uiSpinnerHtml:function(){return"<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>"},_buttonHtml:function(){return"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'><span class='ui-icon "+this.options.icons.up+"'>▲</span>"+"</a>"+"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>"+"<span class='ui-icon "+this.options.icons.down+"'>▼</span>"+"</a>"},_start:function(t){return this.spinning||this._trigger("start",t)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(t,e,i){t=t||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,e,i)},t),this._spin(e*this.options.step,i)},_spin:function(t,e){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+t*this._increment(this.counter)),this.spinning&&this._trigger("spin",e,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(e){var i=this.options.incremental;return i?t.isFunction(i)?i(e):Math.floor(e*e*e/5e4-e*e/500+17*e/200+1):1},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_adjustValue:function(t){var e,i,s=this.options;return e=null!==s.min?s.min:0,i=t-e,i=Math.round(i/s.step)*s.step,t=e+i,t=parseFloat(t.toFixed(this._precision())),null!==s.max&&t>s.max?s.max:null!==s.min&&s.min>t?s.min:t},_stop:function(t){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",t))},_setOption:function(t,e){if("culture"===t||"numberFormat"===t){var i=this._parse(this.element.val());return this.options[t]=e,this.element.val(this._format(i)),void 0}("max"===t||"min"===t||"step"===t)&&"string"==typeof e&&(e=this._parse(e)),"icons"===t&&(this.buttons.first().find(".ui-icon").removeClass(this.options.icons.up).addClass(e.up),this.buttons.last().find(".ui-icon").removeClass(this.options.icons.down).addClass(e.down)),this._super(t,e),"disabled"===t&&(e?(this.element.prop("disabled",!0),this.buttons.button("disable")):(this.element.prop("disabled",!1),this.buttons.button("enable")))},_setOptions:e(function(t){this._super(t),this._value(this.element.val())}),_parse:function(t){return"string"==typeof t&&""!==t&&(t=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(t,10,this.options.culture):+t),""===t||isNaN(t)?null:t},_format:function(t){return""===t?"":window.Globalize&&this.options.numberFormat?Globalize.format(t,this.options.numberFormat,this.options.culture):t},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},_value:function(t,e){var i;""!==t&&(i=this._parse(t),null!==i&&(e||(i=this._adjustValue(i)),t=this._format(i))),this.element.val(t),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:e(function(t){this._stepUp(t)}),_stepUp:function(t){this._start()&&(this._spin((t||1)*this.options.step),this._stop())},stepDown:e(function(t){this._stepDown(t)}),_stepDown:function(t){this._start()&&(this._spin((t||1)*-this.options.step),this._stop())},pageUp:e(function(t){this._stepUp((t||1)*this.options.page)}),pageDown:e(function(t){this._stepDown((t||1)*this.options.page)}),value:function(t){return arguments.length?(e(this._value).call(this,t),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}})}(jQuery),function(t,e){function i(){return++n}function s(t){return t.hash.length>1&&decodeURIComponent(t.href.replace(o,""))===decodeURIComponent(location.href.replace(o,""))}var n=0,o=/#.*$/;t.widget("ui.tabs",{version:"1.10.2",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var e=this,i=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",i.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(e){t(this).is(".ui-state-disabled")&&e.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){t(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs(),i.active=this._initialActive(),t.isArray(i.disabled)&&(i.disabled=t.unique(i.disabled.concat(t.map(this.tabs.filter(".ui-state-disabled"),function(t){return e.tabs.index(t)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):t(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var i=this.options.active,s=this.options.collapsible,n=location.hash.substring(1);return null===i&&(n&&this.tabs.each(function(s,o){return t(o).attr("aria-controls")===n?(i=s,!1):e}),null===i&&(i=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===i||-1===i)&&(i=this.tabs.length?0:!1)),i!==!1&&(i=this.tabs.index(this.tabs.eq(i)),-1===i&&(i=s?!1:0)),!s&&i===!1&&this.anchors.length&&(i=0),i},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):t()}},_tabKeydown:function(i){var s=t(this.document[0].activeElement).closest("li"),n=this.tabs.index(s),o=!0;if(!this._handlePageNav(i)){switch(i.keyCode){case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:n++;break;case t.ui.keyCode.UP:case t.ui.keyCode.LEFT:o=!1,n--;break;case t.ui.keyCode.END:n=this.anchors.length-1;break;case t.ui.keyCode.HOME:n=0;break;case t.ui.keyCode.SPACE:return i.preventDefault(),clearTimeout(this.activating),this._activate(n),e;case t.ui.keyCode.ENTER:return i.preventDefault(),clearTimeout(this.activating),this._activate(n===this.options.active?!1:n),e;default:return}i.preventDefault(),clearTimeout(this.activating),n=this._focusNextTab(n,o),i.ctrlKey||(s.attr("aria-selected","false"),this.tabs.eq(n).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",n)},this.delay))}},_panelKeydown:function(e){this._handlePageNav(e)||e.ctrlKey&&e.keyCode===t.ui.keyCode.UP&&(e.preventDefault(),this.active.focus())},_handlePageNav:function(i){return i.altKey&&i.keyCode===t.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):i.altKey&&i.keyCode===t.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):e},_findNextTab:function(e,i){function s(){return e>n&&(e=0),0>e&&(e=n),e}for(var n=this.tabs.length-1;-1!==t.inArray(s(),this.options.disabled);)e=i?e+1:e-1;return e},_focusNextTab:function(t,e){return t=this._findNextTab(t,e),this.tabs.eq(t).focus(),t},_setOption:function(t,i){return"active"===t?(this._activate(i),e):"disabled"===t?(this._setupDisabled(i),e):(this._super(t,i),"collapsible"===t&&(this.element.toggleClass("ui-tabs-collapsible",i),i||this.options.active!==!1||this._activate(0)),"event"===t&&this._setupEvents(i),"heightStyle"===t&&this._setupHeightStyle(i),e)},_tabId:function(t){return t.attr("aria-controls")||"ui-tabs-"+i()},_sanitizeSelector:function(t){return t?t.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var e=this.options,i=this.tablist.children(":has(a[href])");e.disabled=t.map(i.filter(".ui-state-disabled"),function(t){return i.index(t)}),this._processTabs(),e.active!==!1&&this.anchors.length?this.active.length&&!t.contains(this.tablist[0],this.active[0])?this.tabs.length===e.disabled.length?(e.active=!1,this.active=t()):this._activate(this._findNextTab(Math.max(0,e.active-1),!1)):e.active=this.tabs.index(this.active):(e.active=!1,this.active=t()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var e=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return t("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=t(),this.anchors.each(function(i,n){var o,a,r,h=t(n).uniqueId().attr("id"),l=t(n).closest("li"),c=l.attr("aria-controls");s(n)?(o=n.hash,a=e.element.find(e._sanitizeSelector(o))):(r=e._tabId(l),o="#"+r,a=e.element.find(o),a.length||(a=e._createPanel(r),a.insertAfter(e.panels[i-1]||e.tablist)),a.attr("aria-live","polite")),a.length&&(e.panels=e.panels.add(a)),c&&l.data("ui-tabs-aria-controls",c),l.attr({"aria-controls":o.substring(1),"aria-labelledby":h}),a.attr("aria-labelledby",h)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.element.find("ol,ul").eq(0)},_createPanel:function(e){return t("<div>").attr("id",e).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(e){t.isArray(e)&&(e.length?e.length===this.anchors.length&&(e=!0):e=!1);for(var i,s=0;i=this.tabs[s];s++)e===!0||-1!==t.inArray(s,e)?t(i).addClass("ui-state-disabled").attr("aria-disabled","true"):t(i).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=e},_setupEvents:function(e){var i={click:function(t){t.preventDefault()}};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(e){var i,s=this.element.parent();"fill"===e?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var e=t(this),s=e.css("position");"absolute"!==s&&"fixed"!==s&&(i-=e.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=t(this).outerHeight(!0)}),this.panels.each(function(){t(this).height(Math.max(0,i-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.panels.each(function(){i=Math.max(i,t(this).height("").height())}).height(i))},_eventHandler:function(e){var i=this.options,s=this.active,n=t(e.currentTarget),o=n.closest("li"),a=o[0]===s[0],r=a&&i.collapsible,h=r?t():this._getPanelForTab(o),l=s.length?this._getPanelForTab(s):t(),c={oldTab:s,oldPanel:l,newTab:r?t():o,newPanel:h};e.preventDefault(),o.hasClass("ui-state-disabled")||o.hasClass("ui-tabs-loading")||this.running||a&&!i.collapsible||this._trigger("beforeActivate",e,c)===!1||(i.active=r?!1:this.tabs.index(o),this.active=a?t():o,this.xhr&&this.xhr.abort(),l.length||h.length||t.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(o),e),this._toggle(e,c))},_toggle:function(e,i){function s(){o.running=!1,o._trigger("activate",e,i)}function n(){i.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),a.length&&o.options.show?o._show(a,o.options.show,s):(a.show(),s())}var o=this,a=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),n()}):(i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),r.hide(),n()),r.attr({"aria-expanded":"false","aria-hidden":"true"}),i.oldTab.attr("aria-selected","false"),a.length&&r.length?i.oldTab.attr("tabIndex",-1):a.length&&this.tabs.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),a.attr({"aria-expanded":"true","aria-hidden":"false"}),i.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(e){var i,s=this._findActive(e);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return e===!1?t():this.tabs.eq(e)},_getIndex:function(t){return"string"==typeof t&&(t=this.anchors.index(this.anchors.filter("[href$='"+t+"']"))),t},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){t.data(this,"ui-tabs-destroy")?t(this).remove():t(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var e=t(this),i=e.data("ui-tabs-aria-controls");i?e.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):e.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(i){var s=this.options.disabled;s!==!1&&(i===e?s=!1:(i=this._getIndex(i),s=t.isArray(s)?t.map(s,function(t){return t!==i?t:null}):t.map(this.tabs,function(t,e){return e!==i?e:null})),this._setupDisabled(s))},disable:function(i){var s=this.options.disabled;if(s!==!0){if(i===e)s=!0;else{if(i=this._getIndex(i),-1!==t.inArray(i,s))return;s=t.isArray(s)?t.merge([i],s).sort():[i]}this._setupDisabled(s)}},load:function(e,i){e=this._getIndex(e);var n=this,o=this.tabs.eq(e),a=o.find(".ui-tabs-anchor"),r=this._getPanelForTab(o),h={tab:o,panel:r};s(a[0])||(this.xhr=t.ajax(this._ajaxSettings(a,i,h)),this.xhr&&"canceled"!==this.xhr.statusText&&(o.addClass("ui-tabs-loading"),r.attr("aria-busy","true"),this.xhr.success(function(t){setTimeout(function(){r.html(t),n._trigger("load",i,h)},1)}).complete(function(t,e){setTimeout(function(){"abort"===e&&n.panels.stop(!1,!0),o.removeClass("ui-tabs-loading"),r.removeAttr("aria-busy"),t===n.xhr&&delete n.xhr},1)})))},_ajaxSettings:function(e,i,s){var n=this;return{url:e.attr("href"),beforeSend:function(e,o){return n._trigger("beforeLoad",i,t.extend({jqXHR:e,ajaxSettings:o},s))}}},_getPanelForTab:function(e){var i=t(e).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}})}(jQuery),function(t){function e(e,i){var s=(e.attr("aria-describedby")||"").split(/\s+/);s.push(i),e.data("ui-tooltip-id",i).attr("aria-describedby",t.trim(s.join(" ")))}function i(e){var i=e.data("ui-tooltip-id"),s=(e.attr("aria-describedby")||"").split(/\s+/),n=t.inArray(i,s);-1!==n&&s.splice(n,1),e.removeData("ui-tooltip-id"),s=t.trim(s.join(" ")),s?e.attr("aria-describedby",s):e.removeAttr("aria-describedby")}var s=0;t.widget("ui.tooltip",{version:"1.10.2",options:{content:function(){var e=t(this).attr("title")||"";return t("<a>").text(e).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable()},_setOption:function(e,i){var s=this;return"disabled"===e?(this[i?"_disable":"_enable"](),this.options[e]=i,void 0):(this._super(e,i),"content"===e&&t.each(this.tooltips,function(t,e){s._updateContent(e)}),void 0)},_disable:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s[0],e.close(n,!0)}),this.element.find(this.options.items).addBack().each(function(){var e=t(this);e.is("[title]")&&e.data("ui-tooltip-title",e.attr("title")).attr("title","")})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var e=t(this);e.data("ui-tooltip-title")&&e.attr("title",e.data("ui-tooltip-title"))})},open:function(e){var i=this,s=t(e?e.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),e&&"mouseover"===e.type&&s.parents().each(function(){var e,s=t(this);s.data("ui-tooltip-open")&&(e=t.Event("blur"),e.target=e.currentTarget=this,i.close(e,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._updateContent(s,e))},_updateContent:function(t,e){var i,s=this.options.content,n=this,o=e?e.type:null;return"string"==typeof s?this._open(e,t,s):(i=s.call(t[0],function(i){t.data("ui-tooltip-open")&&n._delay(function(){e&&(e.type=o),this._open(e,t,i) +})}),i&&this._open(e,t,i),void 0)},_open:function(i,s,n){function o(t){l.of=t,a.is(":hidden")||a.position(l)}var a,r,h,l=t.extend({},this.options.position);if(n){if(a=this._find(s),a.length)return a.find(".ui-tooltip-content").html(n),void 0;s.is("[title]")&&(i&&"mouseover"===i.type?s.attr("title",""):s.removeAttr("title")),a=this._tooltip(s),e(s,a.attr("id")),a.find(".ui-tooltip-content").html(n),this.options.track&&i&&/^mouse/.test(i.type)?(this._on(this.document,{mousemove:o}),o(i)):a.position(t.extend({of:s},this.options.position)),a.hide(),this._show(a,this.options.show),this.options.show&&this.options.show.delay&&(h=this.delayedShow=setInterval(function(){a.is(":visible")&&(o(l.of),clearInterval(h))},t.fx.interval)),this._trigger("open",i,{tooltip:a}),r={keyup:function(e){if(e.keyCode===t.ui.keyCode.ESCAPE){var i=t.Event(e);i.currentTarget=s[0],this.close(i,!0)}},remove:function(){this._removeTooltip(a)}},i&&"mouseover"!==i.type||(r.mouseleave="close"),i&&"focusin"!==i.type||(r.focusout="close"),this._on(!0,s,r)}},close:function(e){var s=this,n=t(e?e.currentTarget:this.element),o=this._find(n);this.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&n.attr("title",n.data("ui-tooltip-title")),i(n),o.stop(!0),this._hide(o,this.options.hide,function(){s._removeTooltip(t(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),e&&"mouseleave"===e.type&&t.each(this.parents,function(e,i){t(i.element).attr("title",i.title),delete s.parents[e]}),this.closing=!0,this._trigger("close",e,{tooltip:o}),this.closing=!1)},_tooltip:function(e){var i="ui-tooltip-"+s++,n=t("<div>").attr({id:i,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));return t("<div>").addClass("ui-tooltip-content").appendTo(n),n.appendTo(this.document[0].body),this.tooltips[i]=e,n},_find:function(e){var i=e.data("ui-tooltip-id");return i?t("#"+i):t()},_removeTooltip:function(t){t.remove(),delete this.tooltips[t.attr("id")]},_destroy:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s[0],e.close(n,!0),t("#"+i).remove(),s.data("ui-tooltip-title")&&(s.attr("title",s.data("ui-tooltip-title")),s.removeData("ui-tooltip-title"))})}})}(jQuery);
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/common-functions.tld b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/common-functions.tld new file mode 100644 index 0000000..ffe076d --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/common-functions.tld @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/******************************************************************************* + * Copyright (c) 2012-2015 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--> +<taglib + xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" + version="2.1"> + + <tlib-version>1.0</tlib-version> + <short-name>Winery_Common_Functions</short-name> + <uri>http://www.eclipse.org/winery/functions</uri> + + <!-- from org.eclipse.winery.common.ModelUtilities --> + <function> + <name>winerysPropertiesDefinition</name> + <function-class>org.eclipse.winery.common.ModelUtilities</function-class> + <function-signature>org.eclipse.winery.common.propertydefinitionkv.WinerysPropertiesDefinition getWinerysPropertiesDefinition(org.eclipse.winery.model.tosca.TEntityType)</function-signature> + </function> + + <!-- from org.eclipse.winery.common.Util --> + <function> + <name>convertQNameListToNamespaceToLocalNameList</name> + <function-class>org.eclipse.winery.common.Util</function-class> + <function-signature>java.util.SortedMap convertQNameListToNamespaceToLocalNameList(java.util.List)</function-signature> + </function> + <function> + <name>getType</name> + <function-class>org.eclipse.winery.common.Util</function-class> + <function-signature>org.eclipse.winery.model.tosca.TEntityType getType(org.eclipse.winery.common.interfaces.IWineryRepository, javax.xml.namespace.QName, java.lang.Class)</function-signature> + </function> + <function> + <name>makeCSSName</name> + <function-class>org.eclipse.winery.common.Util</function-class> + <function-signature>java.lang.String makeCSSName(java.lang.String, java.lang.String)</function-signature> + </function> + <function> + <name>XMLAsString</name> + <function-class>org.eclipse.winery.common.Util</function-class> + <function-signature>java.lang.String getXMLAsString(java.lang.Class, java.lang.Object)</function-signature> + </function> + <function> + <name>DOMElementAsString</name> + <function-class>org.eclipse.winery.common.Util</function-class> + <function-signature>java.lang.String getXMLAsString(org.w3c.dom.Element)</function-signature> + </function> + <function> + <name>qname2href</name> + <function-class>org.eclipse.winery.common.Util</function-class> + <function-signature>java.lang.String qname2href(java.lang.String, java.lang.Class, javax.xml.namespace.QName)</function-signature> + </function> + + <!-- from Apache Commons Lang3 --> + <function> + <name>escapeHtml4</name> + <function-class>org.apache.commons.lang3.StringEscapeUtils</function-class> + <function-signature>java.lang.String escapeHtml4(java.lang.String)</function-signature> + </function> +</taglib> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/functions.tld b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/functions.tld new file mode 100644 index 0000000..4157f96 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/functions.tld @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--> +<taglib + xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" + version="2.1"> + + <tlib-version>1.0</tlib-version> + <short-name>Winery_TopologyModeler_Functions</short-name> + <uri>http://www.eclipse.org/winery/topologymodeler/functions</uri> + + <function> + <name>convertQNameWithNameListToNamespaceToLocalNameNamePairList</name> + <function-class>org.eclipse.winery.topologymodeler.WineryUtil</function-class> + <function-signature>java.util.SortedMap convertQNameWithNameListToNamespaceToLocalNameNamePairList(java.util.List)</function-signature> + </function> + + <function> + <name>escapeHtml4</name> + <function-class>org.apache.commons.lang3.StringEscapeUtils</function-class> + <function-signature>java.lang.String escapeHtml4(java.lang.String)</function-signature> + </function> +</taglib> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/about.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/about.tag new file mode 100644 index 0000000..7cb73b2 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/about.tag @@ -0,0 +1,58 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> + +<%@tag description="About for the repository" pageEncoding="UTF-8"%> + +<div class="modal fade" id="about"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Winery Topology Modeler ${project.version}</h4> + </div> + <div class="modal-body"> + <p> Supporting <a href="docs.oasis-open.org/tosca/TOSCA/v1.0/os/TOSCA-v1.0-os.html">TOSCA-v1.0 – + Topology and Orchestration Specification for Cloud Applications Version 1.0. 25 November 2013. OASIS Standard.</a><br/> + <br/> + Part of the <a href="http://www.cloudcycle.org">CloudCycle</a> ecosystem.<br/> + <br/> + Code contributions by Oliver Kopp, Uwe Breitenbücher, Kálmán Képes, Yves Schubert, and Tobias Unger. + </p> + <h3>License</h3> + <p>The Eclipse Foundation makes available all content of this software (“Content”). + Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 (“EPL”) and the and the Apache License 2.0. + A copy of the EPL is available at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. + A copy of the ASL is available at <a href="http://www.apache.org/licenses/LICENSE-2.0.html">http://www.apache.org/licenses/LICENSE-2.0.html</a>. + For purposes of the EPL, “Program” will mean the Content.</p> + <p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (“Redistributor”) and different terms and conditions may apply to your use of any object code in the Content. + Check the Redistributor's license that was provided with the Content. + If no such license exists, contact the Redistributor. + Unless otherwise indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at <a href="http://www.eclipse.org">http://www.eclipse.org</a>.</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" data-dismiss="modal" id="aboutDiagOKButton">Ok</button> + </div> + </div> + </div> +</div> + +<script> +$("#about").on("shown.bs.modal", function() { + $("#aboutDiagOKButton").focus(); +}); + +function showAbout() { + $("#about").modal("show"); +} +</script>
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/QNameChooser.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/QNameChooser.tag new file mode 100644 index 0000000..7a2c067 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/QNameChooser.tag @@ -0,0 +1,46 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Dialog parts for choosing a QName" pageEncoding="UTF-8"%> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions"%> + +<%@attribute name="allQNames" required="true" type="java.util.Collection" description="Collection<QName> of all available QNames" %> +<%@attribute name="includeNONE" required="false" type="java.lang.Boolean" description="Should (none) be included as option?"%> +<%@attribute name="selected" required="false" description="The initial value to select"%> +<%@attribute name="labelOfSelectField" required="true"%> +<%@attribute name="idOfSelectField" required="true"%> + +<div class="form-group"> + <c:if test="${not empty labelOfSelectField}"><label for="${idOfSelectField}" class="control-label">${labelOfSelectField}:</label></c:if> + <select id="${idOfSelectField}" name="${idOfSelectField}" class="form-control"> + <c:if test="${includeNONE}"><option value="(none)">(none)</option></c:if> + <c:forEach var="namespaceEntry" items="${wc:convertQNameListToNamespaceToLocalNameList(allQNames)}"> + <optgroup label="${namespaceEntry.key}"> + <c:forEach var="localName" items="${namespaceEntry.value}"> + <option value="{${namespaceEntry.key}}${localName}">${localName}</option> + </c:forEach> + </optgroup> + </c:forEach> + </select> +</div> + +<script> +$(function(){ + $("#${idOfSelectField}").select2(); + <c:if test="${not empty selected}"> + $("#${idOfSelectField}").select2("val", "${selected}"); + </c:if> +}); +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/artifactcreationdialog.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/artifactcreationdialog.tag new file mode 100644 index 0000000..5e49b04 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/artifactcreationdialog.tag @@ -0,0 +1,443 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Dialog for adding an implementation / deployment artifact" pageEncoding="UTF-8"%> +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@taglib prefix="fup" tagdir="/WEB-INF/tags/common"%> + +<%@attribute name="name" required="true" description="Implementation | Deployment"%> +<%@attribute name="repositoryURL" required="true" description="the URL of Winery's repository"%> +<%@attribute name="onSuccessfulArtifactCreationFunction" required="true" description="javascript code to be executed when the artifact has been successfully created. Parameter: artifactInfo"%> +<%@attribute name="allArtifactTypes" required="true" type="java.util.Collection" description="All available artifact types"%> +<%@attribute name="allNamespaces" required="true" type="java.util.Collection" description="All known namespaces"%> +<%@attribute name="defaultNSForArtifactTemplate" required="true" description="the default namespace of the artifact template"%> + +<%-- either URL or a function to be called for addition --%> +<%@attribute name="URL" required="true" description="the URL of the artifact collection. May also be a function returning the correct URL (used at the topology modeler). I.e., it is an expression being evaluated"%> + +<%@attribute name="isDeploymentArtifact" required="true" type="java.lang.Boolean" description="Is this dialog used to create deployment artifacts?"%> +<%-- required if implementation artifact --%> +<%@attribute name="interfacesOfAssociatedType" type="java.util.List" %> + +<script> +// TODO: check if allArtifactTypes is empty -> then an error message should be shown. Alternative: Add "Manage" button next to "artifact types" + +function addArtifact() { + if (highlightRequiredFields()) { + vShowError("Please fill out required fields."); + return; + } + + var artifactTemplateCreationMode = $("input[name='artifactTemplateCreation']:checked").val(); + var autoCreateArtifactTemplate = (artifactTemplateCreationMode=="createArtifactTemplate"); + + if (autoCreateArtifactTemplate && ($("#artifactTemplateNameIsValid:visible").hasClass("invalid"))) { + vShowError("Please ensure that the artifact template QName is valid."); + return; + } + + + /* begin: form serialization */ + + var theForm = $('#add${name}ArtifactForm'); + + // do not serialze hidden fields + // theForm.find("select:hidden,input:hidden").attr("disabled", "disabled"); + // Because we use "select2", the user-visible select fields are divs. The "real" selects are hidden. + // Therefore, we disable fields manually :) + var disabledFields; + if (artifactTemplateCreationMode == "skipArtifactTemplate") { + disabledFields = ["artifactTemplateName", "artifactTemplateNS", "artifactTemplateToLink"]; + } else if (artifactTemplateCreationMode == "createArtifactTemplate") { + disabledFields = ["artifactTemplateToLink"]; + } else if (artifactTemplateCreationMode == "linkArtifactTemplate") { + disabledFields = ["artifactTemplateName", "artifactTemplateNS", "artifactType"]; + } else { + vShowError("Code not consistent with UI"); + } + + // make a clean form + disabledFields.forEach(function(element) { + $("#"+element).attr("disabled", "disabled"); + }); + $("input[name='artifactTemplateCreation']").attr("disabled", "disabled"); + + // do not serialize choice directly, but ... + // ... append "autoCreateArtifactTemplate=true" in case the artifact template should be auto created + var data = theForm.serialize(); + if (autoCreateArtifactTemplate) { + data = data + "&autoCreateArtifactTemplate=true"; + } + + // enable fields again + disabledFields.forEach(function(element) { + $("#"+element).removeAttr("disabled"); + }); + $("input[name='artifactTemplateCreation']").removeAttr("disabled"); + + /* end: form serialization */ + + <c:if test="${not isDeploymentArtifact}"> + var operationVal = $("#operationName").val(); + if ((operationVal) && (operationVal != "")) { + var operationName = $("#operationName option:selected").text(); + // The operationname is prefixed with the namespace, because of "nextselect" + // we have to undo that effect. + // Therefore, we replace the complete operationName parameter + + var pos = data.indexOf("operationName="); + var posNextParam = data.indexOf("&", pos); + data = data.substr(0, pos) + "operationName=" + operationName + data.substr(posNextParam); + } + </c:if> + + // We assume that the artifact type exists + // i.e., that it was not deleted during loading of the dialog + + // The deployment artifact resource allows auto creation of the artifact template + // We do not need to do that manually using a separate POST call + // TODO: In a future version, this might be better have a clean way to create additional content for an artifact template + + // do the addCall + $.ajax({ + url: ${URL}, + type: "POST", + async: false, + "data": data, + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not create ${name} Artifact", jqXHR, errorThrown); + }, + success: function(data, textStatus, jqXHR) { + // prepare data for onSuccessfulArtifactCreationFunction + // even though interaceName and operationName do not exist at DA, accessing it via jQuery works: then "undefined" is returned, which is OK + var artifactInfo = { + name: $("#artifactName").val(), + interfaceName: $("#interfaceName").val(), + operationName: $("#operationName option:selected").text() + }; + if (artifactTemplateCreationMode == "skipArtifactTemplate") { + // artifactTemplate remains unset as there is not artifactTemplate to be created + artifactInfo.artifactType = $("#artifactType").val(); + } else if (artifactTemplateCreationMode == "createArtifactTemplate") { + artifactInfo.artifactTemplateName = $("#artifactTemplateName").val(); + // FIXME: This is a quick hack - the name could have been changed at the server as it might contain invalid characters for an id + // In other words, $("#artifactTemplateName").val() might not be the localName of the artifactTemplate + artifactInfo.artifactTemplate = "{" + $("#artifactTemplateNS").val() + "}" + $("#artifactTemplateName").val(); + artifactInfo.artifactType = $("#artifactType").val(); + } else if (artifactTemplateCreationMode == "linkArtifactTemplate") { + artifactInfo.artifactTemplateName = $("#artifactTemplateToLink option:selected").text(); + artifactInfo.artifactTemplate = $("#artifactTemplateToLink").val(); + // artifact type is a mandantory field + // we have to ask the artifact template for the QName of its type and then use this data + require(["winery-support-common"], function(wsc) { + var nsAndId = wsc.getNamespaceAndLocalNameFromQName(artifactInfo.artifactTemplate); + var url = makeArtifactTemplateURL("${repositoryURL}", nsAndId.namespace, nsAndId.localname); + $.ajax({ + type: "GET", + async: false, + url: url + "?type", + dataType: "text", + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not get type of artifact template", jqXHR, errorThrown); + return; + }, + success: function(resData, textStatus, jqXHR) { + // QName is directly returned + artifactInfo.artifactType = resData; + } + }); + }); + } else { + vShowError("Code not consistent with UI"); + } + // now, artifactInfo is filled completly + + // the function can be called + ${onSuccessfulArtifactCreationFunction}(artifactInfo); + + $('#add${name}ArtifactDiag').modal('hide'); + vShowSuccess("Artifact added successfully"); + + if (autoCreateArtifactTemplate) { + var aritfactTemplateNS = $("#artifactTemplateNS").val(); + var artifactTemplateName = $("#artifactTemplateName").val(); + var artifactTemplateURL = makeArtifactTemplateURL("${repositoryURL}", aritfactTemplateNS, artifactTemplateName); + $("#artifactTemplateNameAtUploadFiles").text(artifactTemplateName).attr("href", artifactTemplateURL); + var url = artifactTemplateURL + "files/"; + $('#fileupload').fileupload('option', 'url', url); + $("#addFilesToArtifactTemplate").modal('show'); + } + } + }); + +} +</script> + +<c:if test="${not isDeploymentArtifact}"> + <script type="text/javascript" src="${pageContext.request.contextPath}/js/nextselect.js"></script> + <script> + var dependendSelects = {"#interfaceName": "#operationName"}; + var interfaceOpData = { + "": { + label : "(none)", + "options" : [] + }<c:if test="${not empty interfacesOfAssociatedType}">,</c:if> + <c:forEach var="t" items="${interfacesOfAssociatedType}"> + // no label necessary as this list is pre-filled + "${t.name}": { + "options" : [ + "", + <c:forEach var="u" varStatus="loop" items="${t.operationsResouce.listOfAllEntityIdsAsList}"> + "${t.name}:${u}"<c:if test="${!loop.last}">,</c:if> + </c:forEach> + ] + }, + </c:forEach> + <c:forEach var="t" varStatus="outerLoop" items="${interfacesOfAssociatedType}"> + <c:forEach var="u" varStatus="innerLoop" items="${t.operationsResouce.listOfAllEntityIdsAsList}"> + "${t.name}:${u}" : { + "label": "${u}" + }<c:if test="${!innerLoop.last or !outerLoop.last}">,</c:if> + </c:forEach> + </c:forEach> + }; + </script> +</c:if> + +<div class="modal fade" id="add${name}ArtifactDiag"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Add ${name} Artifact</h4> + </div> + <div class="modal-body"> + <form id="add${name}ArtifactForm" enctype="multipart/form-data"> + <fieldset> + + <div class="form-group"> + <label>Name</label> + <input class="form-control" name="artifactName" id="artifactName" type="text" required="required" autocomplete="on" /> + </div> + + <c:if test="${not isDeploymentArtifact}"> + <div class="form-group"> + <label for="interfaceName">Interface Name</label> + <select name="interfaceName" id="interfaceName" class="form-control" onchange="updateListContent(this.value, '#operationName', dependendSelects, interfaceOpData);"> + <option value="" selected="selected">(none)</option> + <c:forEach var="t" items="${interfacesOfAssociatedType}"> + <option value="${t.name}">${t.name}</option> + </c:forEach> + </select> + </div> + + <div class="form-group"> + <label for="operationName">Operation Name</label> + <select name="operationName" id="operationName" class="form-control"> + <%-- options filled by updateListContent defined by nextselect.js --%> + </select> + </div> + </c:if> + + <h4>Artifact Template Creation</h4> + <div class="radio"> + <label> + <input type="radio" name="artifactTemplateCreation" value="createArtifactTemplate" checked="checked" id="createArtifactTemplateInput">Create Artifact Template</input> + </label> + <p class="help-block">Check if you want to upload <strong>new</strong> files, you do not want to reuse existing files and you do not point to an image library.</p> + </div> + <div class="radio"> + <label> + <input type="radio" name="artifactTemplateCreation" value="linkArtifactTemplate">Link Artifact Template</input> + </label> + <p class="help-block">Check if you want to reuse existing files.</p> + </div> + <div class="radio"> + <label> + <input type="radio" name="artifactTemplateCreation" value="skipArtifactTemplate">Do not create an artifact template</input> + </label> + <p class="help-block">Check if you want to point to an image library.</p> + </div> + </fieldset> + <fieldset id="artifactTypeFieldset"> + <div class="form-group" id="artifactTypeDiv"> + <label for="artifactType">Artifact Type</label> + <select name="artifactType" class="form-control" id="artifactType"> + <c:forEach var="t" items="${allArtifactTypes}"> + <option value="${t.toString()}">${t.localPart}</option> + </c:forEach> + </select> + </div> + </fieldset> + <fieldset> + + <fup:artifacttemplateselection allNamespaces="${allNamespaces}" repositoryURL="${repositoryURL}" defaultNSForArtifactTemplate="${defaultNSForArtifactTemplate}"/> + + <div id="linkArtifactTemplate" class="form-group" style="display:none;"> + <label for="divArtifactTemplateToLink">Artifact Template</label> + <div id="divArtifactTemplateToLink"> + <%-- filled by jQuery at openAdd${name}ArtifactDiag() --%> + <select id=artifactTemplateToLink name="artifactTemplate" class="form-control" style="max-width: 90%"> + </select> + <%-- URL is changed each time the selection is changed --%> + <a href="#" target="_blank" class="btn btn-info btn-sm" id="viewArtifactTemplateToLink">view</a> + </div> + </div> + </fieldset> + </form> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> + <button type="button" class="btn btn-primary" onclick="addArtifact();">Add</button> + </div> + </div> + </div> +</div> + + +<script> +function openAdd${name}ArtifactDiag() { + $.ajax({ + url: "${repositoryURL}/artifacttemplates/", + dataType: "json", + async: false, + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not fetch available artifact templates", jqXHR, errorThrown); + }, + success: function(data, textStatus, jqXHR) { + var select = $("#artifactTemplateToLink"); + select.empty(); + $.each(data, function(index, o) { + var qname = "{" + this.namespace + "}" + this.id; + var option = '<option value="' + qname + '">' + this.name + '</option>'; + select.append(option); + if (index==0) { + // first element + // this is the selected element + // we put it as href to the "view" button + $("#viewArtifactTemplateToLink").attr("href", makeArtifactTemplateURL("${repositoryURL}", this.namespace, this.id)); + } + }); + select.trigger("change"); + $('#add${name}ArtifactDiag').modal('show'); + } + }); +} + +requirejs(["select2"], function() { + $("#interfaceName").select2(); + <c:if test="${not isDeploymentArtifact}"> + // the dependend select cannot be a select2 until https://github.com/ivaynberg/select2/issues/1656 is resolved + //$("#operationName").select2(); + </c:if> + $("#artifactType").select2(); + $("#artifactTemplateToLink").select2(); +}); + +requirejs(['tmpl', 'jquery.ui.widget', 'jquery.fileupload', 'jquery.fileupload-ui'], function() { + $('#fileupload').fileupload({ + "autoUpload": true + }); +}); + +$(function(){ + $("input[name='artifactTemplateCreation']").on("change", function(e) { + var choice = $(e.target).attr("value"); + if (choice == "skipArtifactTemplate") { + $(".createArtifactTemplate").hide(); + $("#linkArtifactTemplate").hide(); + $("#artifactTypeFieldset").removeAttr("disabled"); + $("#artifactTypeDiv").show(); + } else if (choice == "createArtifactTemplate") { + $(".createArtifactTemplate").show(); + $("#linkArtifactTemplate").hide(); + $("#artifactTypeFieldset").removeAttr("disabled"); + $("#artifactTypeDiv").show(); + // one might be copy the template name to the artifact template name (if ($("#artifactTemplateName").val() == "")) + } else if (choice == "linkArtifactTemplate") { + $(".createArtifactTemplate").hide(); + $("#linkArtifactTemplate").show(); + $("#artifactTypeFieldset").attr("disabled", "disabled"); + $("#artifactTypeDiv").hide(); + } else { + vShowError("Code not consistent with UI"); + }; + }); + + $("#add${name}ArtifactDiag").on('shown.bs.modal', function() { + $(this).find('form')[0].reset(); + // createArtifactTemplate is the default seeting for the form + // reset the dialog to this choice + $("#createArtifactTemplateInput").trigger("change"); + }); + + $("#artifactName").typing({ + start: function(event, $elem) { + if (syncDAnameWithATname) { + require(["artifacttemplateselection"], function(ats) { + ats.flagArtifactTemplateNameAsUpdating(); + }); + } + }, + stop: function(event, $elem) { + // value is copied at the "change keyup input" event at #artifactName + require(["artifacttemplateselection"], function(ats) { + ats.checkArtifactTemplateName(); + }); + } + }); + + $("#artifactName") + // tip by http://solicitingfame.com/2011/11/09/jquery-keyup-vs-bind/ + .bind("change keyup input", function() { + if (syncDAnameWithATname) { + $("#artifactTemplateName").val(this.value); + } + }) + .on("focus", function() { + syncDAnameWithATname = ($("#artifactTemplateName").is(":visible")) && (this.value == $("#artifactTemplateName").val()); + }); + + $("#artifactTemplateToLink").on("change", function(evt) { + if (evt.val) { + // TODO: possibly use makeArtifactTemplateURL("${repositoryURL}", this.namespace, this.id)) here + require(["winery-support-common"], function(w) { + var fragment = w.getURLFragmentOutOfFullQName(evt.val); + var url = "${repositoryURL}/artifacttemplates/" + fragment + "/"; + $("#viewArtifactTemplateToLink").attr("href", url); + }); + } + }); +}); +</script> + + +<%-- file uploading part --%> + +<div class="modal fade" id="addFilesToArtifactTemplate"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Add files to artifact template <a id="artifactTemplateNameAtUploadFiles"></a></h4> + </div> + <div class="modal-body"> + <fup:jquery-file-upload-full loadexistingfiles="false"></fup:jquery-file-upload-full> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + </div> + <!-- addFilesToArtifactTemplate --> + </div> + </div> +</div> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/artifacttemplateselection.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/artifacttemplateselection.tag new file mode 100644 index 0000000..05c6138 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/artifacttemplateselection.tag @@ -0,0 +1,41 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="dialog for selecting one artifacttemplate" pageEncoding="UTF-8"%> + +<%@attribute name="allNamespaces" required="true" type="java.util.Collection" description="All known namespaces"%> +<%@attribute name="repositoryURL" required="true" description="the URL of Winery's repository"%> +<%@attribute name="defaultNSForArtifactTemplate" required="true" description="the default namespace of the artifact template"%> + +<%@taglib prefix="t" tagdir="/WEB-INF/tags"%> + +<div class="form-group-grouping"> + <!-- createArtifactTemplate class is required for artifactcreationdialog --> + <div class="form-group createArtifactTemplate"> + <label>Artifact Template Name</label> + <!-- name is an NCName --> + <input class="artifactData form-control" id="artifactTemplateName" name="artifactTemplateName" type="text" required="required" autocomplete="on" placeholder="Enter name for artifact template" pattern="[\i-[:]][\c-[:]]*"/> + <div id="artifactTemplateNameIsValid" class="invalid"> + <span id="artifactTemplateNameIsInvalidReason"></span> + </div> + </div> + + <t:namespaceChooser allNamespaces="${allNamespaces}" idOfInput="artifactTemplateNS" selected="${defaultNSForArtifactTemplate}"></t:namespaceChooser> +</div> + +<script> +require(["artifacttemplateselection"], function(ast) { + // configure the plugin + ast.setRepositoryURL("${repositoryURL}"); +}); +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/id_name_type.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/id_name_type.tag new file mode 100644 index 0000000..b88a9a9 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/id_name_type.tag @@ -0,0 +1,36 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Dialog parts for name and type choosing" pageEncoding="UTF-8"%> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions"%> +<%@taglib prefix="w" tagdir="/WEB-INF/tags/common"%> + + +<%@attribute name="allTypes" required="true" type="java.util.Collection" description="Collection<QName> of all available types" %> +<%@attribute name="idPrefix" required="true" description="prefix used for name and type field. E.g., 'Req' becomes 'ReqType'."%> +<%@attribute name="hideIdField" required="false" description="if given, id field is not displayed. Quick hack to have this dialog reusable. Future versions might always show the id dialog and provide sync between name and id"%> + + <c:if test="${not hideIdField}"> + <div class="form-group"> + <label for="${idPrefix}Id" class="control-label">Id:</label> + <input id="${idPrefix}Id" class="form-control" name="${shortName}Name" type="text" required="required" disabled="disabled"/> + </div> + </c:if> + <div class="form-group"> + <label for="${idPrefix}Name" class="control-label">Name:</label> + <input id="${idPrefix}Name" class="form-control" name="${shortName}Name" type="text" required="required" /> + </div> + +<w:QNameChooser allQNames="${allTypes}" idOfSelectField="${idPrefix}Type" labelOfSelectField="Type" /> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/jquery-file-upload-full.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/jquery-file-upload-full.tag new file mode 100644 index 0000000..5bc1057 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/jquery-file-upload-full.tag @@ -0,0 +1,181 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="HTML + JavaScript enabling the full jQuery file upload as shown at http://blueimp.github.com/jQuery-File-Upload/" pageEncoding="UTF-8"%> + +<%-- Original Source https://raw.github.com/blueimp/jQuery-File-Upload/9.5.4/jquery-ui.html, License: MIT; See also CQ 8006 --%> + +<%@attribute name="action" required="false" description="custom action for the upload"%> +<%@attribute name="loadexistingfiles" type="java.lang.Boolean" required="true" description="load existing files from files/ url. false if that should not happen"%> + +<%-- +!! USES HARD-CODED URL "files/" for data !! +It is OK since it is currently only used in "files.jsp" +If it will be used in other places, there has to be a parameter "url" introduced +(and this jsp updated to a tag file) +--%> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + +<%-- The File Upload user interface plugin --%> + + <!-- The file upload form used as target for the file upload widget --> + <form id="fileupload" method="POST" enctype="multipart/form-data" <c:if test="${not empty action}">action="${action}"</c:if>> + <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload --> + <div class="row fileupload-buttonbar"> + <div class="span7"> <!-- should be col-lg-7, but then the add button does not work any more --> + <!-- The fileinput-button span is used to style the file input field as button --> + <span class="btn btn-success fileinput-button"> + <i class="glyphicon glyphicon-plus"></i> + <span>Add files...</span> + <input type="file" name="files[]" multiple> + </span> + </div> + <!-- The global progress information --> + <div class="span5 fileupload-progress fade"> + <!-- The global progress bar --> + <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100"> + <div class="bar" style="width:0%;"></div> + </div> + <!-- The extended global progress information --> + <div class="progress-extended"> </div> + </div> + </div> + <!-- The loading indicator is shown during file processing --> + <div class="fileupload-loading"></div> + <br> + <!-- The table listing the files available for upload/download --> + <table role="presentation" class="table table-striped"><tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery"></tbody></table> + </form> + +<!-- The template to display files available for upload --> +<script id="template-upload" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-upload fade"> + <td> + <span class="preview"></span> + </td> + <td> + <p class="name">{%=file.name%}</p> + <strong class="error text-danger"></strong> + </td> + <td> + <p class="size">Processing...</p> + <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="progress-bar progress-bar-success" style="width:0%;"></div></div> + </td> + <td> + {% if (!i && !o.options.autoUpload) { %} + <button class="btn btn-primary start" disabled> + <i class="glyphicon glyphicon-upload"></i> + <span>Start</span> + </button> + {% } %} + {% if (!i) { %} + <button class="btn btn-warning cancel"> + <i class="glyphicon glyphicon-ban-circle"></i> + <span>Cancel</span> + </button> + {% } %} + </td> + </tr> +{% } %} +</script> +<!-- The template to display files available for download --> +<script id="template-download" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-download fade"> + <td> + <span class="preview"> + {% if (file.thumbnailUrl) { %} + <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" data-gallery><img src="{%=file.thumbnailUrl%}"></a> + {% } %} + </span> + </td> + <td> + <p class="name"> + {% if (file.url) { %} + <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a> + {% } else { %} + <span>{%=file.name%}</span> + {% } %} + </p> + {% if (file.error) { %} + <div><span class="label label-danger">Error</span> {%=file.error%}</div> + {% } %} + </td> + <td> + <span class="size">{%=o.formatFileSize(file.size)%}</span> + </td> + <td> + {% if (file.deleteUrl) { %} + <button class="btn btn-danger btn-sm delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}"{% if (file.deleteWithCredentials) { %} data-xhr-fields='{"withCredentials":true}'{% } %}> + <i class="glyphicon glyphicon-trash"></i> + <span>Delete</span> + </button> + {% } else { %} + <button class="btn btn-warning cancel"> + <i class="glyphicon glyphicon-ban-circle"></i> + <span>Cancel</span> + </button> + {% } %} + </td> + </tr> +{% } %} +</script> + +<script> +/* + * Based on jQuery File Upload Plugin JS Example + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global $, window, document */ + +$(function () { + 'use strict'; + + // jquery.fileupload.process for image resizing capabilities + // has to be included in all cases as jquery.ui.widget depends on it without specifying the depencency explicitly + requirejs(['tmpl', 'jquery.ui.widget', 'jquery.fileupload', 'jquery.fileupload-ui', 'jquery.fileupload-process'], function() { + // Initialize the jQuery File Upload widget: + $('#fileupload').fileupload( { + autoUpload: true, + url: "files/" + }); + + <c:if test="${loadexistingfiles}"> + // Load existing files + $('#fileupload').addClass('fileupload-processing'); + $.ajax({ + url: $('#fileupload').fileupload('option', 'url'), + dataType: 'json', + context: $('#fileupload')[0] + }).always(function () { + $(this).removeClass('fileupload-processing'); + }).done(function (result) { + $(this).fileupload('option', 'done') + .call(this, $.Event('done'), {result: result}); + }).fail(function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not add upload file", jqXHR, errorThrown); + }); + </c:if> + }); +}); +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/orioneditor/orioneditorarea.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/orioneditor/orioneditorarea.tag new file mode 100644 index 0000000..70a1cdf --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/orioneditor/orioneditorarea.tag @@ -0,0 +1,110 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2013-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Wrapper for an orion editing area" pageEncoding="UTF-8"%> + +<%@attribute name="areaid" required="true" description="The id of the editing area."%> +<%@attribute name="withoutsavebutton" required="false"%> +<%@attribute name="initialtext" required="false" description="The value to put in the editor. Can be also passed as body of this tag"%> +<%@attribute name="url" required="false"%> +<%@attribute name="hidden" required="false" description="if not empty, the form is hidden"%> +<%@attribute name="method" required="false" description="the method to use. Defaults to PUT"%> + +<%-- QUICK HACK to change the method from POST to PUT after saving an empty documentation the first time --%> +<%@attribute name="reloadAfterSuccess" required="false" description="Trigger a page reload after success (if true)"%> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions" %> + +<div> + <div id="${areaid}" class="orionxmleditordiv" <c:if test="{$not empty hidden}">style="display: none;"</c:if>><pre>${wc:escapeHtml4(initialtext)}<jsp:doBody/></pre></div> + <c:if test="${empty withoutsavebutton}"> + <button class="btn btn-primary" type="button" onclick="window.winery.orionareas['${areaid}'].save(this);" data-loading-text="Saving...">Save</button> + </c:if> +</div> + +<script> +if (window.winery === undefined) { + window.winery = {}; +} +if (window.winery.orionareas === undefined) { + window.winery.orionareas = {}; +} +require(["orioneditor"], function(edit) { + var config = { + id: "${areaid}", // used for URL update + reloadAfterSuccess : "${reloadAfterSuccess}", + editor: edit({ + contentType: "application/xml", + parent: "${areaid}" + // todo: we can set the initial text by the parameter "contents" + }), + ajaxOptions : { + contentType: "text/xml" + }, + fixEditorHeight: function() { + // fix the editor + // orion puts "height:0px" -> we remove that + $("#${areaid}").removeAttr("style"); + // due to the CSS style, the height is 300px + // "just" adapt the editor to that size + this.editor.resize(); + }, + save: function(button) { + var btn = $(button); // also works if button is undefined + btn.button("loading"); + + var options = this.ajaxOptions; + options.data = this.editor.getText(); + + // ensure that "config" variable is initialized within the ajax call + var config = this; + // the following code does not use "this" anymore as the "this" in the function references to the jqXHR instead of config + $.ajax(options).done(function( data, textStatus, jqXHR ) { + if (data !== undefined) { + // data contains the new id + url = url.replace(config.id, data); + conifg.id = data; + config.ajaxOptions.url = url; + } + if (config.reloadAfterSuccess) { + location.reload(); + } else { + vShowSuccess("sucessfully saved"); + btn.button("reset"); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not add update XML", jqXHR, errorThrown); + btn.button("reset"); + }); + } + }; + + // now, editor is defined + // we cannot "fix" the appearance as the editor height determination does not work on hidden fields + + // url is an optional parameter to the .tag + if ("${url}" != "") { + config.ajaxOptions.url = "${url}"; + } + // method is an optional parameter to the .tag + if ("${method}" == "") { + config.ajaxOptions.type = "PUT"; + } else { + config.ajaxOptions.type = "${method}"; + } + + // store the config in global variable + window.winery.orionareas["${areaid}"] = config; +}); +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/policies/policies.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/policies/policies.tag new file mode 100644 index 0000000..5bd7bd9 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/policies/policies.tag @@ -0,0 +1,54 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2013-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> + +<%-- This is mostly inspired by the reqscaps handling. Future work: Generalize the dialogs somehow to avoid copy'n'paste of code --%> + +<%@tag import="org.apache.taglibs.standard.lang.jstl.test.PageContextImpl"%> +<%@tag description="Renders the list of policies of a node template" pageEncoding="UTF-8"%> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions"%> + +<%@attribute name="list" required="true" type="java.util.List"%> +<%@attribute name="repositoryURL" required="true" type="java.lang.String" %> + +<div class="policiesContainer"> + <div class="header">Policies</div> + <div class="content"> + <div class="row"> + <div class="col-xs-4">Name</div> + <div class="cell col-xs-4">Type</div> + <div class="cell col-xs-4">Template</div> + </div> + <c:forEach var="item" items="${list}" varStatus="loopStatus"><%-- this HTML has to be kept consistent with the tmpl-policy HTML at policydiag.tag --%> + <div class="policy row ${loopStatus.index % 2 == 0 ? 'even' : 'odd'}"> + <div class="col-xs-4 policy name">${item.name}</div> + + <c:set var="clazz" value="<%=org.eclipse.winery.model.tosca.TPolicyType.class%>" /> + <div class="col-xs-4 policy type">${wc:qname2href(repositoryURL, clazz, item.policyType)}</div> + <span class="type">${item.policyType}</span> + + <c:set var="clazz" value="<%=org.eclipse.winery.model.tosca.TPolicyTemplate.class%>" /> + <div class="col-xs-4 policy template">${wc:qname2href(repositoryURL, clazz, item.policyRef)}</div> + <span class="template">${item.policyRef}</span> + + <c:set var="clazz" value="<%=org.eclipse.winery.model.tosca.TPolicy.class%>" /> + <textarea class="policy_xml">${wc:XMLAsString(clazz, item)}</textarea> + </div> + </c:forEach> + <div class="addnewpolicy row" style="display:none;"> + <button class="btn btn-default center-block btn-xs" onclick="showAddDiagForPolicy($(this).parent().parent().parent().parent());">Add new</button> + </div> + </div> +</div> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/policies/policydiag.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/policies/policydiag.tag new file mode 100644 index 0000000..2554b8c --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/policies/policydiag.tag @@ -0,0 +1,205 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> + +<%@tag import="org.eclipse.winery.model.tosca.TPolicyTemplate"%> +<%@tag description="Dialog to add or update a policy. Offers function showUpdateDiagForPolicy(policyElement) / showAddDiagForPolicy(nodeTemplateElement)" pageEncoding="UTF-8"%> +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + +<%@taglib prefix="o" tagdir="/WEB-INF/tags/common/orioneditor"%> +<%@taglib prefix="w" tagdir="/WEB-INF/tags"%> +<%@taglib prefix="ct" tagdir="/WEB-INF/tags/common"%> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions"%> + +<%@attribute name="allPolicyTypes" required="true" type="java.util.Collection" description="Collection<QName> of all available policy types" %> +<%@attribute name="repositoryURL" required="true" type="java.lang.String" description="The URL of winery's repository"%> + +<div class="modal fade" id="PolicyDiag"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Policy</h4> + </div> + <div class="modal-body"> + <ct:id_name_type idPrefix="policy" allTypes="${allPolicyTypes}" hideIdField="true" /> + + <div class="form-group"> + <label for="policyTemplate" class="control-label">Policy Template:</label> + + <input id="policyTemplate" class="form-control" name="policyTemplate"></input> + </div> + + <o:orioneditorarea areaid="OrionpolicyXML" withoutsavebutton="true" /> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" id="deletePolicy" class="btn btn-danger" onclick="deletePolicy();">Delete</button> + <button type="button" id="updatePolicy" class="btn btn-primary" onclick="addOrUpdatePolicy(false);">Update</button> + <button type="button" id="addPolicy" class="btn btn-primary" onclick="addOrUpdatePolicy(true);">Add</button> + </div> + </div> + </div> +</div> + +<c:set var="clazz" value="<%=org.eclipse.winery.model.tosca.TPolicy.class%>" /> +<textarea id="emptyPolicy" class="hidden">${wc:XMLAsString(clazz, null)}</textarea> + +<script> +//global variable set by showUpdateDiagForPolicy and read by addOrUpdatePolicy +var currentPolicyElement; + +// possibly this is a duplicate information as we also have "currentlySelectedNodeTemplate" (or similar) +var currentNodeTemplateElement; + +function updatePolicyTemplateSelect(valueToSelect) { + require(["winery-support-common"], function(w) { + var type = $("#policyType").val(); + var fragment = w.getURLFragmentOutOfFullQName(type); + var url = "${repositoryURL}/policytypes/" + fragment + "/instances/"; + $.ajax(url, { + dataType: 'json' + }).fail(function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not get policy templates", jqXHR, errorThrown); + }).done(function(data) { + // add "(none)" to available items + var none = { + id: "(none)", + text: "(none)" + }; + data.unshift(none); + + if (typeof valueToSelect === "undefined") { + valueToSelect = "(none)"; + } + + $("#policyTemplate") + .select2({data: data}) + .select2("val", valueToSelect); + }); + }); +} + +function showUpdateDiagForPolicy(policyElement) { + currentPolicyElement = policyElement; + + $("#deletePolicy").show(); + $("#updatePolicy").show(); + $("#addPolicy").hide(); + + var name = policyElement.children("div.name").text(); + var type = policyElement.children("span.type").text(); + + $("#policyName").val(name); + $("#policyType").val(type); + + // onchange of type is not called, we have to update the template selection field for ourselves + // we also have to select the current user's choice + updatePolicyTemplateSelect(policyElement.children("span.template").text()); + + var diag = $("#PolicyDiag"); + require(["winery-support-common"], function(w) { + w.replaceDialogShownHookForOrionUpdate(diag, "OrionpolicyXML", currentPolicyElement.children("textarea").val()); + diag.modal("show"); + }); +} + +function showAddDiagForPolicy(nodeTemplateElement) { + currentNodeTemplateElement = nodeTemplateElement; + + $("#deletePolicy").hide(); + $("#updatePolicy").hide(); + $("#addPolicy").show(); + + $("#policyName").val(""); + + // fill policy template select field + updatePolicyTemplateSelect(); + + var diag = $("#PolicyDiag"); + require(["winery-support-common"], function(w) { + w.replaceDialogShownHookForOrionUpdate(diag, "OrionpolicyXML", $("#emptyPolicy").val()); + diag.modal("show"); + }); +} + +function addOrUpdatePolicy(doAdd) { + if (highlightRequiredFields()) { + vShowError("Please fill in all required fields"); + return; + } + + require(["winery-support-common", "tmpl"], function(wsc, tmpl) { + var res = wsc.synchronizeNameAndType("policy", false, [{ + attribute: "policyType", + fieldSuffix: "Type", + }, { + attribute: "policyRef", + fieldSuffix: "Template" + }]); + + if (res) { + var policyTemplate = $("#policyTemplate").select2("data"); + var renderData = { + name: $("#policyName").val(), + + policyTypeText: $("#policyType :selected").text(), + policyTypeVal: $("#policyType").val(), + + policyTemplateText: policyTemplate.text, + policyTemplateVal: policyTemplate.id, + + xml: res.xml + }; + var div = tmpl("tmpl-policy", renderData); + if (doAdd) { + currentNodeTemplateElement.children("div.policiesContainer").children("div.content").children("div.addnewpolicy").before(div); + } else { + currentPolicyElement.replaceWith(div); + } + $("#PolicyDiag").modal("hide"); + } else { + vShowError("Could not synchronize XML fields"); + } + }); +} + + +function deletePolicy() { + // We just have to remove the HTML element: + // The save operation converts the information in the HTML to XML + currentPolicyElement.remove(); + $("#PolicyDiag").modal("hide"); +} + + +$("#policyType") + .select2() + .on("change", updatePolicyTemplateSelect); +</script> + +<%-- parameters: o.id, o.name, o.policyType, o.policyRef, o.xml. Has to be consistent with the HTML generated by policies.tag --%> +<script type="text/x-tmpl" id="tmpl-policy"> + <div class="policy row"> <%-- "even"/"odd" is not set. Could be done by using $.prev() --%> + <div class="col-xs-4 policy name">{%=o.name%}</div> + + <%-- we do not provide a link here. Link is only available at reload. Makes life easier here. makeArtifactTemplateURL at winery-common.js is a first hint of how to generate links --%> + <div class="col-xs-4 policy type">{%=o.policyTypeText%}</div> + <span class="type">{%=o.policyTypeVal%}</span> + + <div class="col-xs-4 policy template">{%=o.policyTemplateText%}</div> + <span class="template">{%=o.policyTemplateVal%}</span> + + <textarea class="policy_xml">{%=o.xml%}</textarea> + </div> +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/spinnerwithinphty.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/spinnerwithinphty.tag new file mode 100644 index 0000000..a77c9d9 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/spinnerwithinphty.tag @@ -0,0 +1,92 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Yves Schubert - initial API and implementation and/or initial documentation + * Oliver Kopp - minor improvements + *******************************************************************************/ +--%> +<%@tag description="A spinner with the possibility to set to inphty via button" pageEncoding="UTF-8"%> +<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + +<%-- Code copied between repository and topology-modeler --%> + +<%-- +Could also be realized as + * HTML5 Web Component (http://www.ibm.com/developerworks/library/wa-html5components1/) or + * x-tags (http://www.x-tags.org/) +We decided to use JSP tags to avoid an additional JavaScript library +--%> + +<%@attribute name="label" required="true"%> +<%@attribute name="id" required="true"%> +<%@attribute name="min"%> +<%@attribute name="max" required="false" description="Maximum value. Default is 1000. The underlying library does not allow arbitrary high values."%> +<%@attribute name="name" required="false" description="The name of the input field. Defaults to the id"%> +<%@attribute name="withinphty" required="false" description="If set, then an inphty button is provded"%> +<%@attribute name="value"%> +<%@attribute name="width" required="false" description="The Column with according to bootstrap rules. Default is 3 (should not be smaller)."%> +<%@attribute name="changedfunction" required="false" description="Called if value changed"%> + +<%-- Set default name value if required --%> +<c:if test="${empty name}"> + <c:set var="name" value="${id}"></c:set> +</c:if> + +<c:if test="${empty width}"> + <c:set var="width" value="3"></c:set> +</c:if> + +<div class="form-group"> + <label for="${id}">${label}</label> + <div class="row"> + <div class="col-lg-${width}"> + <div class="input-group"> + <input id="${id}" class="spinner form-control" name="${name}" type="text" <c:if test="${not empty changedfunction}">onblur="${changedfunction}();"</c:if>/> + <c:if test="${not empty withinphty}"> + <span class="input-group-addon" style="cursor: pointer; border-left:0" onclick="setToInfin('${id}'<c:if test="${not empty changedfunction}">, ${changedfunction}</c:if>);">∞</span> + </c:if> + </div> + </div> + </div> +</div> + +<script> +<%-- +included multiple times. +Drawback when not using HTML5 components and keeping the JavaScript functions closed to the HTML code +--%> +function setToInfin(id, changedFunction) { + var spinner = $("#" + id); + spinner.val('∞'); // &inphty; - jQuery does not decode that, but places the plain text. Therefore, we directly pass the char we want + if (changedFunction !== undefined) { + changedFunction(); + } +} + +$(function() { + var param = {} + <c:if test="${not empty min}"> + param.minimum = "${min}"; + </c:if> + <c:if test="${empty max}"> + param.maximum = 1000; + </c:if> + + // use bootstrap-spinedit plugin + $("#${id}").spinedit(param); + + <c:if test="${not empty changedfunction}"> + $("#${id}").on('valueChanged', ${changedfunction}); + </c:if> + +}); + +</script> + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/CSSForTypes.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/CSSForTypes.tag new file mode 100644 index 0000000..1df2d82 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/CSSForTypes.tag @@ -0,0 +1,50 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Generates style element for node types and relationship types" pageEncoding="UTF-8" %> + +<%@attribute name="nodeTypes" required="true" type="java.util.Collection" %> +<%@attribute name="relationshipTypes" required="true" type="java.util.Collection" %> + +<%@tag import="java.util.Collection"%> +<%@tag import="javax.xml.namespace.QName"%> +<%@tag import="org.eclipse.winery.common.ModelUtilities"%> +<%@tag import="org.eclipse.winery.common.Util"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeType"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipType"%> + +<style> +<% + for (TNodeType nt: (Collection<TNodeType>) nodeTypes) { + String borderColor = ModelUtilities.getBorderColor(nt); + String cssName = Util.makeCSSName(nt.getTargetNamespace(), nt.getName()); +%> + div.NodeTemplateShape.<%=cssName%> { + border-color: <%=borderColor%>; + } +<% + } + + // relationship types CSS + for (TRelationshipType rt: (Collection<TRelationshipType>) relationshipTypes) { + String color = ModelUtilities.getColor(rt); + QName qname = new QName(rt.getTargetNamespace(), rt.getName()); + String cssName = Util.makeCSSName(qname) + "_box"; +%> + div.<%=cssName%> { + background: <%=color%>; + } +<% + } +%> +</style> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/defineCreateConnectorEndpointsFunction.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/defineCreateConnectorEndpointsFunction.tag new file mode 100644 index 0000000..26f139a --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/defineCreateConnectorEndpointsFunction.tag @@ -0,0 +1,42 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Defines the javascript function createConnectorEndpoints globally. Quick hack to avoid huge hacking at the repository" pageEncoding="UTF-8"%> + +<%@tag import="java.util.Collection"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipType"%> +<%@tag import="org.eclipse.winery.common.Util"%> + +<%@attribute name="relationshipTypes" type="java.util.Collection" required="true" %> + +<script> +function createConnectorEndpoints(nodeTemplateShapeSet) { +<% + for (TRelationshipType relationshipType: (Collection<TRelationshipType>) relationshipTypes) { +%> + nodeTemplateShapeSet.find(".<%=Util.makeCSSName(relationshipType.getTargetNamespace(), relationshipType.getName()) %>").each(function(i,e) { + var p = $(e).parent(); + var grandparent = $(p).parent(); + + jsPlumb.makeSource($(e), { + parent:grandparent, + anchor:"Continuous", + connectionType: "{<%=relationshipType.getTargetNamespace()%>}<%=relationshipType.getName()%>", + endpoint:"Blank" + }); + }); +<% + } +%> +} +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/nodeTemplateRenderer.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/nodeTemplateRenderer.tag new file mode 100644 index 0000000..16bc737 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/nodeTemplateRenderer.tag @@ -0,0 +1,266 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - skeletton for node template shapes + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag language="java" pageEncoding="UTF-8" description="This tag is used for both real nodeTemplate node rendering and rendering of a 'template' used to create a nodeTemplateShape. The latter is called by palette.jsp. Therefore, this tag has to be more general."%> +<%-- Parameters --%> + +<%-- template and palette --%> +<%@attribute name="client" required="true" description="IWineryRepository" type="org.eclipse.winery.common.interfaces.IWineryRepository"%> +<%@attribute name="repositoryURL" required="true" type="java.lang.String" description="The URL of winery's repository"%> +<%@attribute name="topologyModelerURI" required="false" type="java.lang.String" description="The URL of winery topology modeler's URI - required for images/. Has to end with '/'. Can be left blank."%> +<%@attribute name="relationshipTypes" description="the known relationship types" required="true" type="java.util.Collection"%> + +<%-- only for topology modeler --%> +<%@attribute name="nodeTemplate" type="org.eclipse.winery.model.tosca.TNodeTemplate"%> +<%@attribute name="top"%> +<%@attribute name="left"%> + +<%-- only for palette.jsp --%> +<%@attribute name="nodeType" type="org.eclipse.winery.model.tosca.TNodeType" %> +<%@attribute name="nodeTypeQName" type="javax.xml.namespace.QName"%> + +<%@tag import="org.eclipse.winery.model.tosca.TArtifactTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TArtifactType"%> +<%@tag import="org.eclipse.winery.model.tosca.TCapability"%> +<%@tag import="org.eclipse.winery.model.tosca.TDeploymentArtifact"%> +<%@tag import="org.eclipse.winery.model.tosca.TDeploymentArtifacts"%> +<%@tag import="org.eclipse.winery.model.tosca.TEntityType.PropertiesDefinition"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeTemplate.Capabilities"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeTemplate.Requirements"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeTemplate.Policies"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeType"%> +<%@tag import="org.eclipse.winery.model.tosca.TPolicy"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipType"%> +<%@tag import="org.eclipse.winery.model.tosca.TRequirement"%> +<%@tag import="org.eclipse.winery.common.ModelUtilities"%> +<%@tag import="org.eclipse.winery.common.Util"%> +<%@tag import="org.eclipse.winery.common.ids.definitions.ArtifactTemplateId"%> +<%@tag import="org.eclipse.winery.common.ids.definitions.ArtifactTypeId"%> +<%@tag import="org.eclipse.winery.common.interfaces.IWineryRepository"%> +<%@tag import="org.w3c.dom.Element" %> +<%@tag import="org.apache.commons.configuration.Configuration"%> +<%@tag import="org.apache.commons.lang3.StringUtils"%> +<%@tag import="org.apache.commons.lang3.StringEscapeUtils"%> +<%@tag import="java.io.StringWriter" %> +<%@tag import="java.util.Collections"%> +<%@tag import="java.util.Collection"%> +<%@tag import="java.util.Iterator"%> +<%@tag import="java.util.List"%> +<%@tag import="java.util.Map"%> +<%@tag import="java.util.UUID"%> +<%@tag import="javax.xml.namespace.QName"%> +<%@tag import="javax.xml.transform.OutputKeys"%> +<%@tag import="javax.xml.transform.Transformer"%> +<%@tag import="javax.xml.transform.TransformerFactory"%> +<%@tag import="javax.xml.transform.dom.DOMSource"%> +<%@tag import="javax.xml.transform.stream.StreamResult"%> + +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates" %> +<%@taglib prefix="ntrq" tagdir="/WEB-INF/tags/common/templates/nodetemplates/reqscaps" %> +<%@taglib prefix="pol" tagdir="/WEB-INF/tags/common/policies" %> +<%@taglib prefix="props" tagdir="/WEB-INF/tags/common/templates" %> + +<% + String visualElementId; + + boolean paletteMode; + if (nodeTemplate == null) { + // we are in palette mode + // --> we render a template to be inserted in the drawing area by drag'n'drop + paletteMode = true; + assert(nodeType != null); + assert(nodeTypeQName != null); + + // these values are only pseudo values, they get all overwritten in drop function of palette.jsp + visualElementId = UUID.randomUUID().toString(); + left = "0"; + top = "0"; + } else { + // we render a real node template + paletteMode = false; + nodeTypeQName = nodeTemplate.getType(); + nodeType = client.getType(nodeTypeQName, TNodeType.class); + if (nodeType == null) { +%> + <script>vShowError("Could not get node type <%=nodeTypeQName%>");</script> +<% + return; + } + + visualElementId = nodeTemplate.getId(); + } + + String nodeTypeCSSName = Util.makeCSSName(nodeTypeQName); +%> + + <div class="NodeTemplateShape unselectable <%=nodeTypeCSSName%> <%if (paletteMode){%> hidden<%}%>" id="<%=visualElementId%>" style="left: <%=left%>px; top: <%=top%>px"> + <div class="headerContainer"> + <img class="icon" onerror="var that=this; require(['winery-common-topologyrendering'], function(wct){wct.imageError(that);});" src="<%=repositoryURL%>/nodetypes/<%=Util.DoubleURLencode(nodeTypeQName)%>/visualappearance/50x50" /> + <% + String name; + if (paletteMode) { + name = ""; // will be changed on drop + } else { + name = nodeTemplate.getName(); + if (StringUtils.isEmpty(name)) { + name = visualElementId; + } + } + %> + <div class="minMaxInstances"> + <span class="minInstances"><% + if (!paletteMode) { + %><%=Util.renderMinInstances(nodeTemplate.getMinInstances())%><% + } + %></span> + <span class="maxInstances"><% + if (!paletteMode) { + %><%=Util.renderMaxInstances(nodeTemplate.getMaxInstances())%><% + } + %></span> + </div> + <div class="id nodetemplate"><%=visualElementId%></div> + <div class="name nodetemplate"><%=name%></div> + <div class="type nodetemplate"><%=Util.qname2hrefWithName(repositoryURL, TNodeType.class, nodeTypeQName, nodeType.getName())%></div> + <span class="typeQName hidden"><%=nodeTypeQName%></span> + <span class="typeNamespace hidden"><%=nodeTypeQName.getNamespaceURI()%></span> + </div> + <div class="endpointContainer"> + <% + for (TRelationshipType relationshipType: (Collection<TRelationshipType>) relationshipTypes) { + %> + <div class="connectorEndpoint <%=Util.makeCSSName(relationshipType.getTargetNamespace(), relationshipType.getName())%>"> + <div class="connectorBox <%=Util.makeCSSName(relationshipType.getTargetNamespace(), relationshipType.getName())%>_box"></div> + <div class="connectorLabel"><%=relationshipType.getName()%></div> + </div> + <% + } + %> + </div> + + <%-- Properties --%> + <props:properties + propertiesDefinition="<%=nodeType.getPropertiesDefinition()%>" + wpd="<%=ModelUtilities.getWinerysPropertiesDefinition(nodeType)%>" + template="<%=paletteMode ? null : nodeTemplate %>" + pathToImages="${topologyModelerURI}images/" /> + + <%-- Deployment Artifacts --%> + + <% + List<TDeploymentArtifact> deploymentArtifacts; + if (paletteMode) { + deploymentArtifacts = Collections.emptyList(); + } else { + TDeploymentArtifacts tDeploymentArtifacts = nodeTemplate.getDeploymentArtifacts(); + if (tDeploymentArtifacts == null) { + deploymentArtifacts = Collections.emptyList(); + } else { + deploymentArtifacts = tDeploymentArtifacts.getDeploymentArtifact(); + } + } + // Render even if (deploymentArtifacts.isEmpty()), because user could add some with drag'n'drop + + // following is required to render artifact specific content + TransformerFactory transFactory = TransformerFactory.newInstance(); + Transformer transformer = transFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + %> + + <div class="deploymentArtifactsContainer"> + + <div class="header">Deployment Artifacts</div> + <div class="content"> + <% + if (!paletteMode) { + for (TDeploymentArtifact deploymentArtifact : deploymentArtifacts) { + %> + <div class="deploymentArtifact row" onclick="showDeploymentArtifactInformation('<%=visualElementId%>', '<%=deploymentArtifact.getName()%>');"> + <textarea class="hidden"><%=org.eclipse.winery.common.Util.getXMLAsString(org.eclipse.winery.model.tosca.TDeploymentArtifact.class, deploymentArtifact)%></textarea> + <div class="col-xs-4 overflowhidden deploymentArtifact name"><%=deploymentArtifact.getName()%></div> + <div class="col-xs-4 overflowhidden artifactTemplate"><% + QName artifactRef; + if ((artifactRef = deploymentArtifact.getArtifactRef()) != null) { + ArtifactTemplateId atId = new ArtifactTemplateId(artifactRef); + %><%=client.getName(atId)%><% + } + %></div> + <div class="col-xs-4 overflowhidden artifactType"><% + ArtifactTypeId atyId = new ArtifactTypeId(deploymentArtifact.getArtifactType()); + %><%=client.getName(atyId)%></div> + </div> + <% + } + } + %> + + <div class="row addDA"> + <button class="btn btn-default btn-xs center-block addDA">Add new</button> + </div> + + <div class="row addnewartifacttemplate"> + <div class="center-block">Drop to add new deployment artifact. Not yet implemented.</div> + </div> + </div> + </div> + + <%-- Requirements and Capabilities --%> + <% + List<TRequirement> reqList; + if (paletteMode) { + reqList = null; + } else { + Requirements reqs = nodeTemplate.getRequirements(); + if (reqs == null) { + reqList = null; + } else { + reqList = reqs.getRequirement(); + } + } + %> + <ntrq:reqs list="<%=reqList%>" repositoryURL="${repositoryURL}" pathToImages="${topologyModelerURI}images/" client="${client}" /> + + <% + List<TCapability> capList; + if (paletteMode) { + capList = null; + } else { + Capabilities caps = nodeTemplate.getCapabilities(); + if (caps == null) { + capList = null; + } else { + capList = caps.getCapability(); + } + } + %> + <ntrq:caps list="<%=capList%>" repositoryURL="${repositoryURL}" pathToImages="${topologyModelerURI}images/" client="${client}"/> + + <%-- Policies --%> + <% + List<TPolicy> policyList; + if (paletteMode) { + policyList = null; + } else { + Policies policies = nodeTemplate.getPolicies(); + if (policies == null) { + policyList = null; + } else { + policyList = policies.getPolicy(); + } + } + %> + <pol:policies list="<%=policyList%>" repositoryURL="${repositoryURL}" /> +</div> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/caps.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/caps.tag new file mode 100644 index 0000000..af70ff6 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/caps.tag @@ -0,0 +1,33 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Renders the list of requirements or capabilties" pageEncoding="UTF-8"%> +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates/reqscaps" %> + +<%@attribute name="client" required="true" description="IWineryRepository" type="org.eclipse.winery.common.interfaces.IWineryRepository"%> +<%@attribute name="list" required="false" type="java.util.List"%> +<%@attribute name="repositoryURL" required="true" %> +<%@attribute name="pathToImages" required="true" description="The path (URI path) to the image/ url, where xml.png is available. Has to end with '/'"%> + +<nt:reqsorcaps + headerLabel="Capabilities" + cssClassPrefix="capabilities" + list="${list}" + shortName="Cap" + TReqOrCapTypeClass="<%=org.eclipse.winery.model.tosca.TCapabilityType.class%>" + repositoryURL="${repositoryURL}" + typeURLFragment="capabilitytypes" + pathToImages="${pathToImages}" + client="${client}" +/> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/reqs.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/reqs.tag new file mode 100644 index 0000000..473443e --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/reqs.tag @@ -0,0 +1,33 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Renders the list of requirements or capabilties" pageEncoding="UTF-8"%> +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates/reqscaps" %> + +<%@attribute name="client" required="true" description="IWineryRepository" type="org.eclipse.winery.common.interfaces.IWineryRepository"%> +<%@attribute name="list" required="false" type="java.util.List"%> +<%@attribute name="repositoryURL" required="true" %> +<%@attribute name="pathToImages" required="true" description="The path (URI path) to the image/ url, where xml.png is available. Has to end with '/'"%> + +<nt:reqsorcaps + headerLabel="Requirements" + cssClassPrefix="requirements" + list="${list}" + shortName="Req" + TReqOrCapTypeClass="<%=org.eclipse.winery.model.tosca.TRequirementType.class%>" + repositoryURL="${repositoryURL}" + typeURLFragment="requirementtypes" + pathToImages="${pathToImages}" + client="${client}" +/> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/reqsorcaps.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/reqsorcaps.tag new file mode 100644 index 0000000..6046730 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/nodetemplates/reqscaps/reqsorcaps.tag @@ -0,0 +1,63 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Renders the list of requirements or capabilties" pageEncoding="UTF-8"%> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions"%> +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates/reqscaps" %> +<%@taglib prefix="props" tagdir="/WEB-INF/tags/common/templates" %> + +<%@attribute name="list" required="false" type="java.util.List" %> +<%@attribute name="headerLabel" required="true" description="Used for the heading" %> +<%@attribute name="cssClassPrefix" required="true" %> +<%@attribute name="TReqOrCapTypeClass" required="true" type="java.lang.Class" %> +<%@attribute name="repositoryURL" required="true" %> +<%@attribute name="typeURLFragment" required="true" description="requirementtypes|capabilitytypes"%> +<%@attribute name="shortName" required="true" description="Used for diag id, function name suffix, Req|Cap"%> +<%@attribute name="client" required="true" description="IWineryRepository" type="org.eclipse.winery.common.interfaces.IWineryRepository"%> +<%@attribute name="pathToImages" required="true" description="The path (URI path) to the image/ url, where xml.png is available. Has to end with '/'"%> + +<%@tag import="org.eclipse.winery.common.ModelUtilities"%> + +<div class="${cssClassPrefix}Container"> + <div class="header">${headerLabel}</div> + <div class="content"> + <c:forEach var="item" items="${list}"> + <div class="reqorcap ${cssClassPrefix} row" id="${item.id}"> + <div class="col-xs-4 id reqorcap">${item.id}</div> + <div class="col-xs-4 name reqorcap">${item.name}</div> + <div class="col-xs-4 type reqorcap">${wc:qname2href(repositoryURL, TReqOrCapTypeClass, item.type)}</div> + <c:set var="type" value="${wc:getType(client, item.type, TReqOrCapTypeClass)}" /> + <props:properties + template="${item}" + propertiesDefinition="${type.propertiesDefinition}" + wpd="${wc:winerysPropertiesDefinition(type)}" + pathToImages="${pathToImages}" /> + </div> + </c:forEach> + <div class="addnewreqorcap row" style="display:none;"> + <button class="btn btn-default btn-xs center-block" onclick="showAddOrUpdateDiagFor${shortName}($(this).parent().parent().parent().parent().attr('id'));">Add new</button> + </div> + </div> +</div> + +<%-- parameters: o.id, o.name, o.type, o.xml. Has to be consistent with above HTML --%> +<script type="text/x-tmpl" id="tmpl-${shortName}"> + <div class="reqorcap ${cssClassPrefix}" id="{%=o.id%}"> + <div class="col-xs-4 id reqorcap">{%=o.id%}</div> + <div class="col-xs-4 name reqorcap">{%=o.name%}</div> + <div class="col-xs-4 type reqorcap">{%#require("winery-support-common").qname2href("${repositoryURL}", "${typeURLFragment}", o.type)%}</div> + <div id="toBeReplacedByProperties"></div> + </div> +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/properties.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/properties.tag new file mode 100644 index 0000000..4cc6f0a --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/properties.tag @@ -0,0 +1,103 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Rendering for properties. A separate CSS has to be provided to style the content. Thus, this tag is reusable both in the topology modeler and in the management UI. Requires global javaScript function editPropertiesXML(visualElementId)" pageEncoding="UTF-8"%> +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + +<%@attribute name="propertiesDefinition" required="true" type="org.eclipse.winery.model.tosca.TEntityType.PropertiesDefinition" description="The TOSCA-conforming properties definition. May be null."%> +<%@attribute name="wpd" required="true" type="org.eclipse.winery.common.propertydefinitionkv.WinerysPropertiesDefinition" description="Winery's K/V properties definition. May be null"%> +<%@attribute name="template" required="true" type="org.eclipse.winery.model.tosca.TEntityTemplate" description="The template to display properties. Has to be null in case of the palette mode of the topology modeler"%> +<%@attribute name="pathToImages" required="true" description="The path (URI path) to the image/ url, where xml.png is available. Has to end with '/'"%> + +<%@tag import="org.eclipse.winery.common.ModelUtilities"%> +<%@tag import="org.eclipse.winery.common.propertydefinitionkv.WinerysPropertiesDefinition"%> +<%@tag import="org.eclipse.winery.common.propertydefinitionkv.PropertyDefinitionKV"%> +<%@tag import="org.eclipse.winery.common.propertydefinitionkv.PropertyDefinitionKVList"%> + +<% +if ((propertiesDefinition != null) || (wpd != null)) { +// properties exist +%> + <div class="propertiesContainer"> + <div class="header">Properties</div> + <div class="content"> + <% + if (wpd == null) { + // no winery's special properties definition, but "normal" TOSCA properties definition + + if (propertiesDefinition.getType() != null) { + %> + <span class="properties_type">XSD Type: <%=propertiesDefinition.getType()%></span> + <% + } else { + %> + <span class="properties_element">XSD Element: <%=propertiesDefinition.getElement()%></span> + <% + } + %> + <textarea class="properties_xml"><% + if (template != null) { + %><%=org.eclipse.winery.common.Util.getXMLAsString(org.eclipse.winery.model.tosca.TEntityTemplate.Properties.class, template.getProperties())%><% + } + %></textarea> + <%-- We have to do use $(this).parent().parent().parent().attr('id') instead of <%=visualElementId%> as on drag'n'drop from the palette, this binding is NOT changed, but the Id changes --> the user does NOT want to edit the properties from the palette entry, but from the node template --%> + <button class="btn btn-default" onclick="editPropertiesXML($(this).parent().parent().parent().attr('id'));"><img src="${pathToImages}xml.png"></img>View</button> + <% + } else { + // Winery special mode + java.util.Properties props; + if (template == null) { + // setting null only because of dump compiler. + // We never read props if in paletteMode + props = null; + } else { + props = ModelUtilities.getPropertiesKV(template); + } + %> + <%-- stores wrapper element name and namespace to ease serialization--%> + <span class="elementName"><%=wpd.getElementName()%></span> + <span class="namespace"><%=wpd.getNamespace()%></span> + <table> + <% + PropertyDefinitionKVList list = wpd.getPropertyDefinitionKVList(); + if (list != null) { + // iterate on all defined properties + for (PropertyDefinitionKV propdef: list) { + String key = propdef.getKey(); + String value; + if (template == null) { + value = ""; + } else { + // assign value, but change "null" to "" if no property is defined + if ((value = props.getProperty(key)) == null) { + value = ""; + } + } + %> + <tr class="KVProperty"> + <td><span class="<%= key %> KVPropertyKey"><%= key %></span></td> + <td><a class="KVPropertyValue" href="#" data-type="text" data-title="Enter <%= key %>"><%=value %></a></td> + </tr> + <% + } + } + %> + </table> + <% + } + %> + </div> + </div> +<% +} +%> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/propertiesBasic.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/propertiesBasic.tag new file mode 100644 index 0000000..5e850b1 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/propertiesBasic.tag @@ -0,0 +1,112 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Has to be included once before ussage of properties.tag. Used in topology modeler and in properties.jsp of entitytemplates" pageEncoding="UTF-8"%> + +<%@tag import="org.eclipse.winery.common.constants.Namespaces" %> + +<%@taglib prefix="o" tagdir="/WEB-INF/tags/common/orioneditor"%> + +<div class="modal fade" id="PropertyXMLDiag"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Property</h4> + </div> + <div class="modal-body"> + <!-- embed the XML editor without a save button. We provide the save button by ourselves --> + <o:orioneditorarea areaid="PropertyXML" withoutsavebutton="true"/> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> + <button type="button" class="btn btn-primary" onclick="savePropertiesXMLChanges();">Save</button> + </div> + </div> + </div> +</div> + +<script> +// global variable set by editPropertiesXML and read by savePropertiesXMLChanges +var nodeTemplateIdsTextAreaPropertiesXMLEditing; + +function editPropertiesXML(nodeTemplateId) { + // code mirror does not update content if field not fully shown + // therefore, we hook in into the "shown" event + $("#PropertyXMLDiag").off("shown.bs.modal"); + $("#PropertyXMLDiag").on("shown.bs.modal", function() { + nodeTemplateIdsTextAreaPropertiesXMLEditing = $("#" + nodeTemplateId).children(".propertiesContainer").children(".content").children("textarea"); + var val = nodeTemplateIdsTextAreaPropertiesXMLEditing.val(); + window.winery.orionareas["PropertyXML"].editor.setText(val); + window.winery.orionareas["PropertyXML"].fixEditorHeight(); + }); + $("#PropertyXMLDiag").modal("show"); + nodeTemplateIdSelectedForPropertiesXMLEditing = nodeTemplateId; +} + +function savePropertiesXMLChanges() { + var val = window.winery.orionareas["PropertyXML"].editor.getText(); + nodeTemplateIdsTextAreaPropertiesXMLEditing.text(val); + $("#PropertyXMLDiag").modal("hide"); +} + +/** + * @param properties - the properties div + * @param w - an XMLWriter + */ +function savePropertiesFromDivToXMLWriter(properties, w, writeNamespaceDeclaration) { + if (properties.length != 0) { + // properties exist + var contentDiv = properties.children("div.content"); + var xmlProperties = contentDiv.children("textarea.properties_xml"); + if (xmlProperties.length == 0) { + // K/V properties -> winery special: XSD defined at node type + + var elementName = contentDiv.children("span.elementName").text(); + var namespace = contentDiv.children("span.namespace").text(); + + w.writeStartElement("Properties"); + if (writeNamespaceDeclaration){ + w.writeAttributeString("xmlns", "<%=Namespaces.TOSCA_NAMESPACE%>"); + } + w.writeStartElement(elementName); + w.writeAttributeString("xmlns", namespace); + properties.children("div.content").children("table").children("tbody").children("tr").each(function() { + var keyEl = $(this).children("td:first-child"); + var key = keyEl.text(); + var valueEl = keyEl.next().children("a"); + var value; + if (valueEl.hasClass("editable-empty")) { + value = ""; + } else { + value = valueEl.text(); + value = value.replace(/\]\]>/g, "]]]]><![CDATA[>"); + value = "<![CDATA[" + value + "]]>"; + } + w.writeStartElement(key); + w.writeString(value); + w.writeEndElement(); + }); + w.writeEndElement(); + // close "Properties" + w.writeEndElement(); + } else { + // just put the content of the XML field as child of the current node template + // The sourrinding tag "properties" is included in the XML field + var data = xmlProperties.text(); + w.writeXML(data); + } + } + +} +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/registerConnectionTypesAndConnectNodeTemplates.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/registerConnectionTypesAndConnectNodeTemplates.tag new file mode 100644 index 0000000..fb55f9e --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/registerConnectionTypesAndConnectNodeTemplates.tag @@ -0,0 +1,205 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Registers the connection types to jsPlumb" pageEncoding="UTF-8"%> + +<%@attribute name="relationshipTypes" description="the known relationship types" required="true" type="java.util.Collection"%> +<%@attribute name="relationshipTemplates" description="the relationship templates to render" required="true" type="java.util.Collection"%> +<%@attribute name="ondone" description="JavaScript code executed when everything has been done" required="false"%> +<%@attribute name="repositoryURL" required="true" %> +<%@attribute name="readOnly" required="false" type="java.lang.Boolean"%> + +<%@tag import="java.util.Collection"%> +<%@tag import="org.eclipse.winery.model.tosca.TRequirement"%> +<%@tag import="org.eclipse.winery.model.tosca.TCapability"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipType"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipTemplate.SourceElement"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipTemplate.TargetElement"%> +<%@tag import="org.eclipse.winery.common.Util"%> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> + +<% +Collection<TRelationshipType> relationshipTypesCasted = (Collection<TRelationshipType>) relationshipTypes; +Collection<TRelationshipTemplate> relationshipTemplatesCasted = (Collection<TRelationshipTemplate>) relationshipTemplates; +%> + +<script> +// we should load jquery and jquery.ui to have it available at jsPlumb +// however, jquery.ui cannot be loaded as it conflicts with bootstrap (when using toogle buttons) +require(["jquery", "jsplumb", "winery-common-topologyrendering"], function(globallyavailablea, jsPlumb, wct) { + jsPlumb.bind("ready", function() { + /** + * Shows error if req or cap does not exist + */ + function getNodeTemplateIdForReqOrCapId(id) { + var reqOrCap = $("#" + id); + if (reqOrCap.length == 0) { + vShowError("Requirement/Capability with id " + id + " not found"); + } + var res = reqOrCap.parent().parent().parent().attr("id"); + return res; + } + + // register the "selected" type to enable selection of arrows + jsPlumb.registerConnectionTypes({ + "selected":{ + paintStyle:{ strokeStyle:"red", lineWidth:5 }, + hoverPaintStyle:{ lineWidth: 7 } + } + }); + +<% + int i=0; + String when = ""; + String whenRes = ""; + if (relationshipTypesCasted.isEmpty()) { +%> + vShowError("No relationship types exist. Please add relationship types to the repository"); +<% + } + for (TRelationshipType relationshipType: relationshipTypesCasted) { + String fnName = "ajaxRTdata" + i; + when = when + fnName + "(), "; + whenRes = whenRes + fnName + ", "; +%> + function <%=fnName%>() { return $.ajax({ + url: "<%=repositoryURL%>/relationshiptypes/<%=Util.DoubleURLencode(relationshipType.getTargetNamespace())%>/<%=Util.DoubleURLencode(relationshipType.getName())%>/visualappearance/", + dataType: "json", + success: function(data, textStatus, jqXHR) { + jsPlumb.registerConnectionType( + "{<%=relationshipType.getTargetNamespace()%>}<%=relationshipType.getName()%>", + data); + }, + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not load relationship type {<%=relationshipType.getTargetNamespace()%>}<%=relationshipType.getName()%>", jqXHR, errorThrown); + } + });}; +<% + i++; + } + + if (!relationshipTypesCasted.isEmpty()) { + // strip last comma + when = when.substring(0, when.length()-2); + whenRes = whenRes.substring(0, whenRes.length()-2); + } +%> + // as soon as all relationship types are registered as jsPlumb object, + // create connection end points and connect the nodes + $.when(<%=when%>).done(function(<%=whenRes%>){ + require(["winery-common-topologyrendering"], function(wct) { + // A NodeTemplateShape also appears in the palette. There, it is hidden. + // These should not be initialized as the template will be initialized later on + + <c:if test="${readOnly}">wct.setReadOnly();</c:if> + + // Quick hack: All node templates are draggable, even in the readonly view + wct.initNodeTemplate(jsPlumb.getSelector(".NodeTemplateShape:not('.hidden')"), true); + + var sourceId; + var targetId; +<% + for (TRelationshipTemplate relationshipTemplate : relationshipTemplatesCasted) { +%> + var req = undefined; + var cap = undefined; +<% + // Source: Either NodeTemplate or Requirement + SourceElement sourceElement = relationshipTemplate.getSourceElement(); + if (sourceElement == null) { + %>vShowError("sourceElement is null for <%=relationshipTemplate.getId()%>");<% + continue; + } + Object source = sourceElement.getRef(); + if (source instanceof TRequirement) { +%> + req = "<%=((TRequirement)source).getId()%>"; + sourceId = getNodeTemplateIdForReqOrCapId(req); +<% + } else { + TNodeTemplate sourceT = (TNodeTemplate) source; + if (sourceT == null) { + %>vShowError("sourceElement.getRef() is null for <%=relationshipTemplate.getId()%>");<% + continue; + } +%> + sourceId = "<%=sourceT.getId()%>"; +<% + } + + // Target: Either NodeTemplate or Requirement + TargetElement targetElement = relationshipTemplate.getTargetElement(); + if (targetElement == null) { + %>vShowError("targetElement is null for <%=relationshipTemplate.getId()%>");<% + continue; + } + Object target = targetElement.getRef(); + if (target instanceof TCapability) { +%> + cap = "<%=((TCapability)target).getId()%>"; + targetId = getNodeTemplateIdForReqOrCapId(cap); +<% + } else { + TNodeTemplate targetT = (TNodeTemplate) target; + if (targetT == null) { + %>vShowError("targetElement.getRef() is null for <%=relationshipTemplate.getId()%>");<% + continue; + } +%> + targetId = "<%=targetT.getId()%>"; +<% + } +%> + var c = jsPlumb.connect({ + source: sourceId, + target: targetId, + type:"<%=relationshipTemplate.getType()%>" + }); + wct.handleConnectionCreated(c); + // we have to store the TOSCA id as jsPlumb does not allow to pass ids from user's side + // we could overwrite c.id, but we are not aware the side effects... + winery.connections[c.id].id = "<%=relationshipTemplate.getId()%>"; + if (req) { + winery.connections[c.id].req = req; + } + if (cap) { + winery.connections[c.id].cap = cap; + } +<% + if (relationshipTemplate.getName() != null) { +%> + winery.connections[c.id].name = "<%=relationshipTemplate.getName()%>"; +<% + } + } +%> + + // all connections are there + // we can register the events now + + jsPlumb.bind("connection", wct.handleConnectionCreated); + + ${ondone} + + // end of the when waiting for all relationship types + }); + // end of require binding + }); + // jsPlumb.ready + }); +// requirejs +}); +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/toggleButtons.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/toggleButtons.tag new file mode 100644 index 0000000..05ed289 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/templates/toggleButtons.tag @@ -0,0 +1,131 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Toggle buttons for visual appearance" pageEncoding="UTF-8"%> +<script> +var JQUERY_ANIMATION_DURATION = 400; + +function doShowOrHide(elements, showThem) { + if (elements.length == 0) { + // e.g., no properties defined + return; + } + if (showThem) { + elements.slideDown(); + } else { + elements.slideUp(); + } + window.setTimeout(function() { + jsPlumb.repaint(elements.parent()); + }, JQUERY_ANIMATION_DURATION); +} + +function showIds(cb) { + var elements; + if ($("div.NodeTemplateShape.selected").size() > 0) { + elements = $("div.NodeTemplateShape.selected div.id.nodetemplate"); + } else { + elements = $("div.NodeTemplateShape:visible div.id.nodetemplate"); + } + + if ($(cb).hasClass("active")) { + elements.fadeOut(); + } else { + elements.fadeIn(); + } + // no repaint required as no nodes are moved + // window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION); +} + +/** + * Toogles visiblity of both node types and relationship types + */ +function showTypes(showThem) { + // types at node templates + var elements; + var typesOfRelationshipTemplates; + if ($("div.NodeTemplateShape.selected").size() > 0) { + elements = $("div.NodeTemplateShape.selected div.type.nodetemplate"); + // TODO: We should put into typesOfRelationshipTemplates all type divs of relationshiptemplates connecting highlighted node templates + // This should be done when doing the multiselect + // And there should be a second if similar to the node templates for relationship templates + typesOfRelationshipTemplates = $(".todo"); + } else { + elements = $("div.NodeTemplateShape:visible div.type.nodetemplate"); + // TODO: we should check for a single relationship template being selected + typesOfRelationshipTemplates = $(".relationshipTypeLabel"); + } + + if (showThem) { + elements.fadeIn(); + typesOfRelationshipTemplates.fadeIn(); + } else { + elements.fadeOut(); + typesOfRelationshipTemplates.fadeOut(); + } + + // no repaint required as no nodes are moved + // window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION); +} + +function showOrHideProperties(showThem) { + var elements; + if ($("div.NodeTemplateShape.selected").size() > 0) { + elements = $("div.NodeTemplateShape.selected > div.propertiesContainer"); + } else { + elements = $("div.NodeTemplateShape:visible > div.propertiesContainer"); + } + doShowOrHide(elements, showThem); +} + + +function showOrHideDeploymentArtifacts(showThem) { + var elements; + if ($("div.NodeTemplateShape.selected").size() > 0) { + elements = $("div.NodeTemplateShape:visible.selected > div.deploymentArtifactsContainer"); + } else { + elements = $("div.NodeTemplateShape:visible > div.deploymentArtifactsContainer"); + } + doShowOrHide(elements, showThem); +} + +function showOrHideReqCaps(showThem) { + var elements; + if ($("div.NodeTemplateShape.selected").size() > 0) { + elements = $("div.NodeTemplateShape:visible.selected > div.requirementsContainer, div.NodeTemplateShape:visible.selected > div.capabilitiesContainer"); + } else { + elements = $("div.NodeTemplateShape:visible > div.requirementsContainer, div.NodeTemplateShape:visible > div.capabilitiesContainer"); + } + doShowOrHide(elements, showThem); +} + +function showOrHidePolicies(showThem) { + var elements; + if ($("div.NodeTemplateShape.selected").size() > 0) { + elements = $("div.NodeTemplateShape:visible.selected > div.policiesContainer"); + } else { + elements = $("div.NodeTemplateShape:visible > div.policiesContainer"); + } + doShowOrHide(elements, showThem); +} +</script> + +<div class="btn-group" data-toggle="buttons-checkbox" id="toggleButtons"> + <button class="btn btn-default" id="toggleIdVisibility" onclick="showIds(this);">Ids</button> + <button class="btn active" id="toggleTypeVisibility" onclick="showTypes(!$(this).hasClass('active'));">Types</button> + <button class="btn btn-default" id="togglePropertiesVisibility" onclick="showOrHideProperties(!$(this).hasClass('active'));">Properties</button> + <button class="btn btn-default" id="toggleDeploymentArtifactsVisibility" onclick="showOrHideDeploymentArtifacts(!$(this).hasClass('active'));">Deployment Artifacts</button> + <button class="btn btn-default" id="toggleReqCapsVisibility" onclick="showOrHideReqCaps(!$(this).hasClass('active'));">Requirements & Capabilities</button> + <button class="btn btn-default" id="PoliciesVisibility" onclick="showOrHidePolicies(!$(this).hasClass('active'));">Policies</button> +</div> + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/nodeTemplateSelector.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/nodeTemplateSelector.tag new file mode 100644 index 0000000..9d09fd3 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/nodeTemplateSelector.tag @@ -0,0 +1,230 @@ +<% +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ +%> +<%@tag language="java" pageEncoding="UTF-8" description="This tag is used to render Node and Relationship Templates for selection in a dialog."%> + +<%-- attributes for the NodeTemplate selection --%> +<%@attribute name="templateURL" type="java.lang.String"%> +<%@attribute name="topologyName" type="java.lang.String"%> +<%@attribute name="topologyNamespace" type="java.lang.String"%> +<%@attribute name="repositoryURL" type="java.lang.String" %> +<%@attribute name="stName" type="java.lang.String" %> +<%@attribute name="choices" type="java.util.Map<org.eclipse.winery.model.tosca.TNodeTemplate, java.util.Map<org.eclipse.winery.model.tosca.TNodeTemplate, java.util.List<org.eclipse.winery.model.tosca.TEntityTemplate>>>"%> + +<%@tag import="java.util.ArrayList"%> +<%@tag import="java.util.HashMap"%> +<%@tag import="java.util.List"%> +<%@tag import="java.util.Map"%> +<%@tag import="java.util.UUID"%> +<%@tag import="javax.xml.namespace.QName"%> +<%@tag import="org.eclipse.winery.model.tosca.TEntityTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipType"%> +<%@tag import="org.eclipse.winery.repository.client.WineryRepositoryClientFactory"%> +<%@tag import="org.eclipse.winery.repository.client.IWineryRepositoryClient"%> +<%@tag import="org.eclipse.winery.common.Util"%> + +<%@taglib prefix="ntrq" tagdir="/WEB-INF/tags/common/templates/nodetemplates/reqscaps" %> +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates" %> + +<div id="nodeTemplateSelector"> + <p> There are several possible Node Templates to be inserted. <br> Please select your desired NodeTemplate: </p> + + <% + // the pixel distance between the displayed NodeTemplates + final int NODE_TEMPLATE_DISTANCE = 150; + + IWineryRepositoryClient client = WineryRepositoryClientFactory.getWineryRepositoryClient(); + client.addRepository(repositoryURL); + + // instantiate variables + Map<String, String> idMap = new HashMap<String, String>(); + List<TRelationshipTemplate> possibleConnections = new ArrayList<TRelationshipTemplate>(); + String sourceId = ""; + String randomId = ""; + String id = ""; + + // a counter used for an ID + int counter = 0; + + // used for the position of the NodeTemplate in the EditorArea + int topCounter = 0; + %> + <script> + // array to collect the created IDs + IDs = new Array(); + + // save all created connections in an array to be able to detach them after the selection + Connections = new Array(); + </script> + <% + // render a topology for every choice to be displayed in the dialog + for (TNodeTemplate nt: choices.keySet()) { + + Map<TNodeTemplate, List<TEntityTemplate>> entityTemplates = choices.get(nt); + + for (TNodeTemplate choice: entityTemplates.keySet()) { + id = "choice" + Integer.toString(counter); + + %> + <div id="proposalEditorArea"> + <div id="proposaldrawingarea"> + <% + + topCounter = 0; + %> + <nt:nodeTemplateRenderer client="<%=client%>" relationshipTypes="<%=client.getAllTypes(TRelationshipType.class)%>" repositoryURL='<%=repositoryURL%>' nodeTemplate="<%=nt%>" top="<%=Integer.toString(topCounter)%>" left='<%="0"%>'/> + <script> + + //Map IDs here. ID mapping is necessary to avoid conflict with the modelled NodeTemplates in the background. + <% + randomId = UUID.randomUUID().toString(); + %> + document.getElementById("<%=nt.getId()%>").id = "<%=randomId%>"; + IDs.push("<%=randomId%>"); + <% + idMap.put(nt.getId(), randomId); + %> + </script> + <% + + topCounter = topCounter + NODE_TEMPLATE_DISTANCE; + %> + <!-- use the nodeTemplateRenderer tag to render NodeTemplates--> + <nt:nodeTemplateRenderer client="<%=client%>" relationshipTypes="<%=client.getAllTypes(TRelationshipType.class)%>" repositoryURL='<%=repositoryURL%>' nodeTemplate="<%=choice%>" top="<%=Integer.toString(topCounter)%>" left='<%="0"%>'/> + <script> + //Map IDs here + <% + randomId = UUID.randomUUID().toString(); + %> + document.getElementById("<%=choice.getId()%>").id = "<%=randomId%>"; + IDs.push("<%=randomId%>"); + <% + idMap.put(choice.getId(), randomId); + %> + </script> + </div> + </div> + <% if (entityTemplates.get(choice).size() > 1) { %> + <p> There are several possible Relationship Templates to connect the Node Templates <%=nt.getName()%> and <%=choice.getName()%>. Please choose at least one connection: </p> + <%} + for (TEntityTemplate rtChoice: entityTemplates.get(choice)) { + + TRelationshipTemplate connector = (TRelationshipTemplate) rtChoice; + if (entityTemplates.get(choice).size() > 1) { + %> + <input checked="checked" id="<%=connector.getName()%>" name="<%=connector.getName()%>" type="checkbox" value="<%=connector.getName()%>"> <%=connector.getName()%> <br/> + <% + } + sourceId = ((TNodeTemplate) connector.getSourceElement().getRef()).getId(); + String targetId = ((TNodeTemplate) connector.getTargetElement().getRef()).getId(); + QName type = connector.getType(); + String visualSourceId = idMap.get(sourceId); + String visualTargetId = idMap.get(targetId); + %> + <script> + // connect the rendered NodeTemplates + require(["winery-common-topologyrendering"], function(wct) { + wct.initNodeTemplate(jsPlumb.getSelector(".NodeTemplateShape:not('.hidden')"), true); + }); + var c; + require(["jsplumb"], function(_jsPlumb) { + _jsPlumb.ready(function() { + c = _jsPlumb.connect({ + source:"<%=visualSourceId%>", + target:"<%=visualTargetId%>", + endpoint:"Blank", + type: "<%=type%>" + }); + Connections.push(c); + }) + }); + + </script> + <%} + %> + <br> + <button type="button" class="btn btn-primary btn-default" data-dismiss="modal" id="<%=id%>" value='<%=choice.getName()%>' onclick="onSelected<%=choice.getName()%>()">Use Template: <%=choice.getName()%></button> + <script> + + /** + * Handles a click on the "Select" button. + * + * This selection handler method is created for every NodeTemplate that can be chosen by the user. + * This is realized by inserting the unique names of the NodeTemplate choices in the method name via JSP scriptlet. + */ + function onSelected<%=choice.getName()%>() { + + SelectedRTs = new Array(); + + for (var i = 0; i < Connections.length; i++) { + jsPlumb.detach(Connections[i]); + } + + <% + if (entityTemplates.get(choice).size() == 1) { + %> + SelectedRTs.push("<%=((TRelationshipTemplate) entityTemplates.get(choice).get(0)).getName()%>"); + <% + } else if (entityTemplates.get(choice).size() > 1) { + for (TEntityTemplate rtChoice: entityTemplates.get(choice)) { + TRelationshipTemplate connector = (TRelationshipTemplate) rtChoice; + %> + if (document.getElementById("<%=connector.getName()%>").checked) { + SelectedRTs.push(document.getElementById("<%=connector.getName()%>").value); + } + <% + }} + %> + SelectedItems = new Array(); + SelectedItems.push(document.getElementById("<%=id%>").value); + + if (SelectedItems.length == 0) { + vShowError("Please selected at least one Relationship Template."); + } else { + // add the selected Templates to the topology and restart the completion + var selectedNodeTemplates = JSON.stringify(SelectedItems); + var selectedRelationshipTemplates = JSON.stringify(SelectedRTs); + $.post("jsp/topologyCompletion/selectionHandler.jsp", {topology: topology, allChoices: choices, selectedNodeTemplates: selectedNodeTemplates, selectedRelationshipTemplates: selectedRelationshipTemplates}, + function(data){ + require(["winery-topologycompletion"], function(completer) { + completer.restartCompletion(data, document.getElementById('overwriteTopology').checked,document.getElementById('openInNewWindow').checked, + document.getElementById('topologyName').value, document.getElementById('topologyNamespace').value, true, "<%=stName%>", + "<%=templateURL%>", "<%=repositoryURL%>"); + }); + } + ); + } + } + </script> + <% + counter++; + } + }%> + <br> + <br> + <br> + <button type="button" class="btn btn-primary btn-default" data-dismiss="modal" id="cancel" onclick="onSelectedCancel()">Cancel Automatic Completion</button> + <p><i> Press this button if you want to continue the completion manually.</i> </p> + <script> + // save topology and refresh the page + function onSelectedCancel() { + $.post("jsp/topologyCompletion/topologySaver.jsp", {topology: topology, templateURL: "<%=templateURL%>", repositoryURL: "<%=repositoryURL%>", topologyName: "<%=topologyName%>", topologyNamespace: "<%=topologyNamespace%>", overwriteTopology: "true"}, + function(callback){ + document.location.reload(true); + } + ); + } + </script> +</div>
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/relationshipTemplateSelector.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/relationshipTemplateSelector.tag new file mode 100644 index 0000000..58e2d85 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/relationshipTemplateSelector.tag @@ -0,0 +1,158 @@ +<% +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ +%> + +<%@tag language="java" pageEncoding="UTF-8" description="This tag is used to render Relationship Templates for selection in a dialog."%> + +<%-- attributes for the topology selection --%> +<%@attribute name="templateURL" type="java.lang.String"%> +<%@attribute name="topologyName" type="java.lang.String"%> +<%@attribute name="topologyNamespace" type="java.lang.String"%> +<%@attribute name="repositoryURL" type="java.lang.String" %> +<%@attribute name="stName" type="java.lang.String" %> +<%@attribute name="choices" type="java.util.List<org.eclipse.winery.model.tosca.TEntityTemplate>"%> + +<%@tag import="java.util.ArrayList"%> +<%@tag import="java.util.HashMap"%> +<%@tag import="java.util.List"%> +<%@tag import="java.util.Map"%> +<%@tag import="java.util.UUID"%> +<%@tag import="javax.xml.namespace.QName"%> +<%@tag import="org.eclipse.winery.model.tosca.TEntityTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipType"%> +<%@tag import="org.eclipse.winery.repository.client.WineryRepositoryClientFactory"%> +<%@tag import="org.eclipse.winery.repository.client.IWineryRepositoryClient"%> +<%@tag import="org.eclipse.winery.common.Util"%> + +<%@taglib prefix="ntrq" tagdir="/WEB-INF/tags/common/templates/nodetemplates/reqscaps" %> +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates" %> +<%@taglib prefix="tc" tagdir="/WEB-INF/tags/common/topologycompletion"%> + +<div id="relationshipTemplateSelector"> +<p> There are several possible Relationship Templates for a connection. <br> Please select your desired connection(s): </p> +<script> + // save all created connections in an array to be able to detach them after the selection + Connections = new Array(); +</script> +<% + // the pixel distance between the displayed NodeTemplates + final int NODE_TEMPLATE_DISTANCE = 150; + + IWineryRepositoryClient client = WineryRepositoryClientFactory.getWineryRepositoryClient(); + client.addRepository(repositoryURL); + + Map<String, String> idMap = new HashMap<String, String>(); + String sourceId = ""; + String id = "choice"; + + // used for the position of the NodeTemplate in the EditorArea + int topCounter = 0; + + List<TRelationshipTemplate> possibleConnections = new ArrayList<TRelationshipTemplate>(); + + for (TEntityTemplate choice: choices) { + if (choice instanceof TRelationshipTemplate) { + possibleConnections.add((TRelationshipTemplate) choice); + } + } + for (TRelationshipTemplate connector: possibleConnections) { %> + <div id="proposalEditorArea"> + <div id="proposaldrawingarea"> + <div id="allRelationships"> + <% + topCounter = 0; + + for (TEntityTemplate choice: choices) { + if (choice instanceof TNodeTemplate) { + TNodeTemplate nodeTemplate = (TNodeTemplate) choice; + + topCounter = topCounter + NODE_TEMPLATE_DISTANCE; + %> + <nt:nodeTemplateRenderer client="<%=client%>" relationshipTypes="<%=client.getAllTypes(TRelationshipType.class)%>" repositoryURL='<%=repositoryURL%>' nodeTemplate="<%=nodeTemplate%>" top="<%=Integer.toString(topCounter)%>" left='<%="0"%>'/> + <script> + //Map IDs here + <% + String randomId = UUID.randomUUID().toString(); + %> + document.getElementById("<%=nodeTemplate.getId()%>").id = "<%=randomId%>"; + <% + idMap.put(nodeTemplate.getId(), randomId); + %> + </script> + <% + } + } + + sourceId = ((TNodeTemplate) connector.getSourceElement().getRef()).getId(); + String targetId = ((TNodeTemplate) connector.getTargetElement().getRef()).getId(); + QName type = connector.getType(); + + String visualSourceId = idMap.get(sourceId); + String visualTargetId = idMap.get(targetId); + %> + <script> + // connect the rendered NodeTemplates + require(["winery-common-topologyrendering"], function(wct) { + wct.initNodeTemplate(jsPlumb.getSelector(".NodeTemplateShape:not('.hidden')"), true); + }); + var c; + require(["jsplumb"], function(_jsPlumb) { + _jsPlumb.ready(function() { + c = _jsPlumb.connect({ + source:"<%=visualSourceId%>", + target:"<%=visualTargetId%>", + endpoint:"Blank", + type: "<%=type%>" + }); + Connections.push(c); + }) + }); + </script> + </div> + </div> + </div> + <input id="<%=id%>" name="<%=id%>" type="checkbox" value="<%=connector.getName()%>"> <%=connector.getName()%> <br> + + <%}%> + <button type="button" class="btn btn-primary btn-default" id="btnUseSelection" onclick="useRelationshipTemplateSelection()">Use Selection</button> + <script> + function useRelationshipTemplateSelection() { + // add the selected RelationshipTemplates to the topology and restart the completion + SelectedItems = new Array(); + for (var i= 0; i < document.getElementById("rtchoices").children[0].choice.length; i++) { + if (document.getElementById("rtchoices").children[0].choice[i].checked == true) { + SelectedItems.push(document.getElementById("rtchoices").children[0].choice[i].value); + } + } + + if (SelectedItems.length == 0) { + vShowError("Please selected at least one Relationship Template."); + } else { + $('#chooseRelationshipTemplateDiag').modal('hide'); + var selectedRelationshipTemplates = JSON.stringify(SelectedItems); + // add selected RelationshipTemplate(s) to the topology + $.post("jsp/topologyCompletion/selectionHandler.jsp", {topology: topology, allChoices: choices, selectedRelationshipTemplates: selectedRelationshipTemplates}, + function(data) { + require(["winery-topologycompletion"], function(completer) { + completer.restartCompletion(data, document.getElementById('overwriteTopology').checked,document.getElementById('openInNewWindow').checked, + topologyName, topologyNamespace, true, "<%=stName%>", + "<%=templateURL%>", "<%=repositoryURL%>"); + }); + } + ); + } + } + </script> +</div> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/selectionDialogs.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/selectionDialogs.tag new file mode 100644 index 0000000..9edfced --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/selectionDialogs.tag @@ -0,0 +1,175 @@ +<% +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ +%> +<%@tag language="java" pageEncoding="UTF-8" description="This tag is used to create DIVs for the selection dialogs."%> + +<%@attribute name="repositoryURL" type="java.lang.String"%> +<%@attribute name="serviceTemplateName" type="java.lang.String"%> +<%@attribute name="topologyTemplateURL" type="java.lang.String"%> + +<!-- + Topology Completion: chooseRelationshipTemplateDiag. + This dialog serves the user selection of inserted RelationshipTemplates whenever there are several possibilities. +--> +<div class="modal fade" id="chooseRelationshipTemplateDiag"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Topology Completion - Relationship Template Selection</h4> + </div> + <div class="modal-body"></div> + <div class="modal-footer"></div> + </div> + </div> +</div> + +<!-- + Topology Completion: chooseNodeTemplateDiag. + This dialog serves the user selection of inserted Node and RelationshipTemplates when the user selects "Complete topology step-by-step". +--> +<div class="modal fade" id="chooseNodeTemplateDiag"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Topology Completion - Step by Step</h4> + </div> + <div class="modal-body"></div> + <div class="modal-footer"> + <button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button> + </div> + </div> + </div> +</div> + +<!-- + Topology Completion: chooseTopologyDiag. + This dialog serves the user selection of completed topologies. +--> +<div class="modal fade" id="chooseTopologyDiag"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Topology Completion - Choose possible solution </h4> + </div> + <div class="modal-body"></div> + <div class="modal-footer"> + <button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button> + </div> + </div> + </div> +</div> + +<!-- + Topology Completion: enterCompletionInformationDiag. + This dialog serves the input of information before completing a topology automatically. +--> +<div class="modal fade" id="enterCompletionInformationDiag"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Topology Completion</h4> + </div> + <div class="modal-body"> + <form id="enterCompletionInformationForm" enctype="multipart/form-data"> + <fieldset> + <p> Select Save Option: </p> + <p> <input type="radio" id="overwriteTopology" name="overwriteTopology" onclick="document.getElementById('topologyNamespace').disabled = true; document.getElementById('topologyName').disabled = true; document.getElementById('openInNewWindow').disabled = true;" checked> Overwrite Topology<br> + <input type="radio" id="overwriteTopology" name="overwriteTopology" onclick="document.getElementById('topologyNamespace').disabled = false; document.getElementById('topologyName').disabled = false;document.getElementById('openInNewWindow').disabled = false;"> Create new Topology </p> <p> Name: <input id="topologyName" name="topologyName" disabled="disabled" type="text" size="30" maxlength="30"> </p> <p>Namespace: <input id="topologyNamespace" name="topologyNamespace" disabled="disabled" type="text" size="50" maxlength="60"> </p> + <input id="openInNewWindow" name="openInNewWindow" type="checkbox" disabled="disabled" /> Open Topology in new Window <br> + <input id="completionStyle" name="completionStyle" type="checkbox" /> Complete Topology Step-by-Step + </fieldset> + </form> + </div> + <div class="modal-footer"> + <button type="button" class="btn" data-dismiss="modal">Cancel</button> + <button type="button" class="btn btn-primary btn-default" id="btnCompleteTopology" onclick="onClickCompleteTopology()">Complete Topology</button> + <script> + function onClickCompleteTopology() { + var namespace = document.getElementById('topologyNamespace').value; + var validURIregexp = new RegExp("([A-Za-z][A-Za-z0-9+\\-.]*):(?:(//)(?:((?:[A-Za-z0-9\\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})*)@)?((?:\\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\\.[A-Za-z0-9\\-._~!$&'()*+,;=:]+)\\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*))(?::([0-9]*))?((?:/(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)|/((?:(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?)|((?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)|)(?:\\?((?:[A-Za-z0-9\\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*))?(?:\#((?:[A-Za-z0-9\\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*))?"); + if (!document.getElementById('overwriteTopology').checked && document.getElementById('topologyName').value == "") { + vShowError("Please enter a name for the new topology."); + } else if (!document.getElementById('overwriteTopology').checked && (document.getElementById('topologyNamespace').value == "" || !validURIregexp.test(namespace))) { + vShowError("Please enter a valid name space for the new topology."); + } + else { + $('#enterCompletionInformationDiag').modal('hide'); + + require(["winery-topologycompletion"], function(completer) { + completer.complete(document.getElementById('overwriteTopology').checked,document.getElementById('openInNewWindow').checked,document.getElementById('topologyName').value, document.getElementById('topologyNamespace').value, document.getElementById('completionStyle').checked, + "<%=repositoryURL%>", "<%=serviceTemplateName%>", "<%=topologyTemplateURL%>"); + }); + + } + } + </script> + </div> + </div> + </div> +</div> + +<script> + $(function() { + chooseRelationshipTemplateDiag = $('#chooseRelationshipTemplateDiag'); + + chooseRelationshipTemplateDiag.on('show', function() { + $(this).find('form')[0].reset(); + }); + + chooseNodeTemplateDiag = $('#chooseNodeTemplateDiag'); + + chooseNodeTemplateDiag.on('show', function() { + $(this).find('form')[0].reset(); + }); + + chooseNodeTemplateDiag.on('hidden.bs.modal', function () { + for (var i = 0; i < Connections.length; i++) { + jsPlumb.detach(Connections[i]); + } + $(document.getElementById("nodeTemplateSelector")).remove(); + }); + + chooseTopologyDiag = $('#chooseTopologyDiag'); + + chooseTopologyDiag.on('show', function() { + $(this).find('form')[0].reset(); + }); + + chooseTopologyDiag.on('hidden.bs.modal', function () { + for (var i = 0; i < Connections.length; i++) { + jsPlumb.detach(Connections[i]); + } + $(document.getElementById("topologyTemplateSelector")).remove(); + }); + + enterCompletionInformationDiag = $('#enterCompletionInformationDiag'); + + enterCompletionInformationDiag.on('show', function() { + $(this).find('form')[0].reset(); + }); + }); + + /** + * This function is invoked when the button "Complete Topology" is + * selected. It will open a dialog to enter necessary information for the + * completion. + */ + function completeTopology() { + // show the dialog to enter information for the topology completion + enterCompletionInformationDiag.modal("show"); + } +</script>
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/topologyTemplateSelector.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/topologyTemplateSelector.tag new file mode 100644 index 0000000..0d73a57 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/common/topologycompletion/topologyTemplateSelector.tag @@ -0,0 +1,246 @@ +<% +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ +%> + +<%@tag language="java" pageEncoding="UTF-8" description="This tag is used to render Topology Templates for selection in a dialog."%> + +<%-- attributes for the topology selection --%> +<%@attribute name="templateURL" type="java.lang.String"%> +<%@attribute name="topologyName" type="java.lang.String"%> +<%@attribute name="topologyNamespace" type="java.lang.String"%> +<%@attribute name="repositoryURL" type="java.lang.String" %> +<%@attribute name="solutionTopologies" type="java.util.List<org.eclipse.winery.model.tosca.TTopologyTemplate>"%> + +<%@tag import="java.io.StringWriter"%> +<%@tag import="java.util.HashMap"%> +<%@tag import="java.util.Map"%> +<%@tag import="java.util.List"%> +<%@tag import="java.util.UUID"%> +<%@tag import="javax.xml.bind.Marshaller"%> +<%@tag import="javax.xml.bind.JAXBContext"%> +<%@tag import="javax.xml.bind.JAXBException"%> +<%@tag import="javax.xml.namespace.QName"%> +<%@tag import="org.eclipse.winery.model.tosca.Definitions"%> +<%@tag import="org.eclipse.winery.model.tosca.TEntityTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TNodeTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TRelationshipType"%> +<%@tag import="org.eclipse.winery.model.tosca.TServiceTemplate"%> +<%@tag import="org.eclipse.winery.model.tosca.TTopologyTemplate"%> +<%@tag import="org.eclipse.winery.repository.client.WineryRepositoryClientFactory"%> +<%@tag import="org.eclipse.winery.repository.client.IWineryRepositoryClient"%> +<%@tag import="org.eclipse.winery.common.Util"%> + +<%@taglib prefix="ntrq" tagdir="/WEB-INF/tags/common/templates/nodetemplates/reqscaps" %> +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates"%> + +<div id="topologyTemplateSelector"> +<p> There are several possible topology solutions <br> Please select your desired topology: </p> + <script> + // array to collect the created IDs + IDs = new Array(); + + // save all created connections in an array to be able to detach them after the selection + Connections = new Array(); + </script> +<% + // the pixel distance between the displayed NodeTemplates + final int NODE_TEMPLATE_DISTANCE = 150; + + List<TTopologyTemplate> topologyTemplateSelector = solutionTopologies; + int i = 0; + int counter = 0; + Map<String, String> idMap; + for (TTopologyTemplate choice: topologyTemplateSelector) { + Definitions definitions = new Definitions(); + TServiceTemplate st = new TServiceTemplate(); + st.setTopologyTemplate(choice); + definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().add(st); + JAXBContext context = JAXBContext.newInstance(Definitions.class); + Marshaller m = context.createMarshaller(); + StringWriter stringWriter = new StringWriter(); + + m.marshal(definitions, stringWriter); + int topCounter = 0; + IWineryRepositoryClient client = WineryRepositoryClientFactory.getWineryRepositoryClient(); + client.addRepository(repositoryURL); + String id = "solution" + Integer.toString(i); + + String sourceId = null; + idMap = new HashMap<String, String>(); + + %> + <div id="proposalEditorArea"> + <div id="proposaldrawingarea"> + <div id="<%=counter%>"> + <script> IDs.push("<%=id%>"); </script> + <% + for (TEntityTemplate entity: choice.getNodeTemplateOrRelationshipTemplate()) { + + if (entity instanceof TNodeTemplate) { + TNodeTemplate nodeTemplate = (TNodeTemplate) entity; + + %> + <nt:nodeTemplateRenderer client="<%=client%>" relationshipTypes="<%=client.getAllTypes(TRelationshipType.class)%>" repositoryURL='<%=repositoryURL%>' nodeTemplate="<%=nodeTemplate%>" top="<%=Integer.toString(topCounter)%>" left='<%="0"%>'/> + + <% + String randomId = UUID.randomUUID().toString(); + %> + <script> + document.getElementById("<%=nodeTemplate.getId()%>").id = "<%=randomId%>"; + </script> + <% + topCounter = topCounter + NODE_TEMPLATE_DISTANCE; + idMap.put(nodeTemplate.getId(), randomId); + %> + + <% + } + } + for (TEntityTemplate entity: choice.getNodeTemplateOrRelationshipTemplate()) { + if (entity instanceof TRelationshipTemplate) { + TRelationshipTemplate connector = (TRelationshipTemplate) entity; + sourceId = ((TNodeTemplate) connector.getSourceElement().getRef()).getId(); + String visualSourceId = idMap.get(sourceId); + String targetId = ((TNodeTemplate) connector.getTargetElement().getRef()).getId(); + String visualTargetId = idMap.get(targetId); + QName type = connector.getType(); + %> + <script type='text/javascript'> + var c; + require(["winery-common-topologyrendering"], function(wct) { + wct.initNodeTemplate(jsPlumb.getSelector(".NodeTemplateShape:not('.hidden')"), true); + require(["jsplumb"], function(_jsPlumb) { + _jsPlumb.ready(function() { + c = _jsPlumb.connect({ + source:"<%=visualSourceId%>", + target:"<%=visualTargetId%>", + endpoint:"Blank", + type: "<%=type%>" + }); + Connections.push(c); + }) + }); + wct.handleConnectionCreated(c); + }); + </script> <% + } + } + %> + </div> + </div> + </div> + <br> + <input name="<%=id%>" id="<%=id%>" type="checkbox" value='<%=stringWriter.toString()%>' onclick="onClick<%=id%>()"> Save this Topology + <script> + /** + * Handles a click on the "Save this Topology" checkbox. + */ + function onClick<%=id%>() { + if (document.getElementById('<%=id%>').checked) { + document.getElementById('<%=id + "overwrite"%>').disabled = false; + document.getElementById('<%=id + "name"%>').disabled = false; + document.getElementById('<%=id + "namespace"%>').disabled = false; + document.getElementById('<%=id + "newWindow"%>').disabled = false; + } + else { + document.getElementById('<%=id + "overwrite"%>').disabled = true; + document.getElementById('<%=id + "name"%>').disabled = true; + document.getElementById('<%=id + "namespace"%>').disabled = true; + document.getElementById('<%=id + "newWindow"%>').disabled = true; + } + } + </script> + <input disabled="disabled" name='<%=id + "overwrite"%>' id='<%=id + "overwrite"%>' type="checkbox" onclick='onClick<%=id + "overwrite"%>()'> Overwrite current Topology + <script> + /** + * Handles a click on the "Overwrite current Topology" checkbox. + */ + function onClick<%=id + "overwrite"%>() { + if (document.getElementById('<%=id + "overwrite"%>').checked) { + document.getElementById('<%=id + "name"%>').disabled = true; + document.getElementById('<%=id + "namespace"%>').disabled = true; + document.getElementById('<%=id + "newWindow"%>').disabled = true; + } else { + document.getElementById('<%=id + "name"%>').disabled = false; + document.getElementById('<%=id + "namespace"%>').disabled = false; + document.getElementById('<%=id + "newWindow"%>').disabled = false; + } + } + </script> + <input disabled="disabled" name='<%=id + "newWindow"%>' id='<%=id + "newWindow"%>' type="checkbox"> Open in new Window <br> <br> + <p>Name: <input disabled="disabled" id='<%=id + "name"%>' name='<%=id + "name"%>' value="<%=topologyName%>" type="text" size="30" maxlength="30"> </p> + <p>Namespace: <input disabled="disabled" id='<%=id + "namespace"%>' value="<%=topologyNamespace%>" name='<%=id + "namespace"%>' type="text" size="50" maxlength="60"> </p> + <% + counter++; + i++; + } +%> + <button type="button" id="save" class="btn btn-primary btn-default">Save Topologies</button> + <script> + $('#save').on('click', function() { + + for (var i = 0; i < IDs.length; i++) { + if (document.getElementById(IDs[i]).checked) { + + var name = document.getElementById(IDs[i] + 'name').value; + var namespace = document.getElementById(IDs[i] + 'namespace').value; + var overwrite = document.getElementById(IDs[i] + 'overwrite').checked; + var openInNewWindow = document.getElementById(IDs[i] + 'newWindow').checked; + + // check validity of the namespace + var validURIregexp = new RegExp("([A-Za-z][A-Za-z0-9+\\-.]*):(?:(//)(?:((?:[A-Za-z0-9\\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})*)@)?((?:\\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\\.[A-Za-z0-9\\-._~!$&'()*+,;=:]+)\\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*))(?::([0-9]*))?((?:/(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)|/((?:(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?)|((?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)|)(?:\\?((?:[A-Za-z0-9\\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*))?(?:\#((?:[A-Za-z0-9\\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*))?"); + if (validURIregexp.test(namespace) || overwrite) { + + if (!overwrite) { + // first create a new service template via AJAX call + var dataToSend = "name=" + name + "&namespace=" + namespace; + var url = "<%=repositoryURL%>" + "/servicetemplates/"; + $.ajax( + { + type: "POST", + async: false, + url: url, + "data": dataToSend, + dataType: "text", + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not add Service Template."); + } + }); + } + + // now save the topology template + $.post("jsp/topologyCompletion/topologySaver.jsp", {topology: document.getElementById(IDs[i]).value, templateURL: "<%=templateURL%>", repositoryURL: "<%=repositoryURL%>", topologyName: name, topologyNamespace: namespace, overwriteTopology: overwrite}, + function(data){ + if (openInNewWindow) { + // a new topology has been created, open it in a new window + var win=window.open('?repositoryURL=' + "<%=repositoryURL%>" + '&ns='+ namespace + '&id=' + name, '_blank'); + win.focus(); + } else if (overwrite) { + // refresh page + document.location.reload(true); + } + // close the dialog + chooseTopologyDiag.modal("hide"); + vShowSuccess("Successfully Saved Topologies.") + } + ); + } else { + vShowError("Please enter a valid namespace."); + } + } + } + }); + </script> +</div> + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/idInput.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/idInput.tag new file mode 100644 index 0000000..86dae1c --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/idInput.tag @@ -0,0 +1,43 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="input field for an id unique in the target namespace. Current implementation: Unique in the topology modeler" pageEncoding="UTF-8"%> + +<%@attribute name="inputFieldId" required="true" description="The name and id of the input field"%> + +<div class="form-group" id="${inputFieldId}Group"> + <label for="${inputFieldId}" class="control-label">Id:</label> + <input id="${inputFieldId}" class="form-control" name="${inputFieldId}" type="text" required="required" /> +</div> + +<script> +$("#${inputFieldId}").typing({ + stop: function(evt, elem) { + // check for existinance in the current model + // TODO: global check using the backend + var isSuccess; + try { + var val = elem.val(); + isSuccess = (val != "") && ($("#" + elem.val()).length == 0); + } catch(err) { + // all syntax errors are invalid inputs + isSuccess = false; + } + var newClass = (isSuccess? "has-success" : "has-error"); + var div = elem.parent(); + if (!div.hasClass(newClass)) { + div.removeClass("has-error").removeClass("has-success").addClass(newClass); + } + } +}); +</script>
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/namespaceChooser.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/namespaceChooser.tag new file mode 100644 index 0000000..25a19e3 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/namespaceChooser.tag @@ -0,0 +1,52 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="places a bootstrap form control to chooose a namespace. A new namespace can be created" pageEncoding="UTF-8"%> + +<!-- + This tag is shared at repository and topologytemplate. + Both versions differ from each other. + In the repository, ns.decoded is used. + In the topology modeler only "ns" is used: + In other words: The topology modeler passes a Collection<String>, whereas repository passes Collection<Namespace> + --> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + +<%@attribute name="allNamespaces" required="true" type="java.util.Collection" description="All known namespaces as strings (because the topology modeler currently doesn't provide that as list of winery namespace objects)"%> +<%@attribute name="idOfInput" required="true" description="The id if the input field storing the namespace. Also used as name"%> +<%@attribute name="nameOfInput" required="false" description="The name if the input field storing the namespace. If not provided, ifOfInput is used"%> +<%@attribute name="selected" description="The currently selected namespace (optional)"%> + +<c:if test="${empty nameOfInput}"><c:set var="nameOfInput" value="${idOfInput}"></c:set></c:if> + +<!-- createArtifactTemplate class is required for artifactcreationdialog --> +<div class="form-group createArtifactTemplate"> + <label for="${idOfInput}" class="control-label">Namespace</label> + <input type="hidden" class="form-control" name="${nameOfInput}" id="${idOfInput}"></input> +</div> + +<script> +// we have to use data as select2 does not allow "createSearchChoice" when using <select> as underlying html element +$("#${idOfInput}").select2({ + createSearchChoice: function(term) { + // enables creation of new namespaces + return {id:term, text:term}; + }, + data:[ + <c:forEach var="ns" items="${allNamespaces}" varStatus="loop"> + {id:"${ns}",text:"${ns}"}<c:if test="${!loop.last}">,</c:if> + </c:forEach> + ] +}).select2("val", "${selected}"); +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/palette.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/palette.tag new file mode 100644 index 0000000..e6363f6 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/palette.tag @@ -0,0 +1,207 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - initial API and implementation and/or initial documentation + * Oliver Kopp - improvements + *******************************************************************************/ +--%> +<%@tag language="java" pageEncoding="UTF-8" description="Renders the palette on the left"%> + +<%@attribute name="repositoryURL" required="true" type="java.lang.String"%> +<%@attribute name="client" required="true" description="IWineryRepository" type="org.eclipse.winery.common.interfaces.IWineryRepository"%> +<%@attribute name="relationshipTypes" description="the known relationship types" required="true" type="java.util.Collection"%> + +<%@tag import="javax.xml.namespace.QName" %> +<%@tag import="java.util.Collection"%> +<%@tag import="java.util.UUID"%> +<%@tag import="java.util.List"%> +<%@tag import="org.eclipse.winery.common.interfaces.IWineryRepository" %> +<%@tag import="org.eclipse.winery.model.tosca.TNodeType"%> +<%@tag import="org.eclipse.winery.common.Util" %> + +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates" %> + +<link rel="stylesheet" href="css/palette.css" /> + +<div id="palette"> + +<div id="paletteLabel"> +Palette +</div> + +<% + Collection<TNodeType> allNodeTypes = client.getAllTypes(TNodeType.class); + if (allNodeTypes.isEmpty()) { +%> + <script> + vShowError("No node types exist. Please add node types in the repository."); + </script> + <% + } + for (TNodeType nodeType: allNodeTypes) { + if (nodeType.getName() == null) { + System.err.println("Invalid nodetype in ns " + nodeType.getTargetNamespace()); + continue; + } +%> + <div class="paletteEntry"> + <div class="iconContainer"> + <img class="icon" onerror="var that=this; require(['winery-common-topologyrendering'], function(wct){wct.imageError(that);});" src="<%= repositoryURL %>/nodetypes/<%= Util.DoubleURLencode(nodeType.getTargetNamespace()) %>/<%=Util.DoubleURLencode(nodeType.getName())%>/visualappearance/50x50" /> + </div> + <div class="typeContainer"> + <div class="typeContainerMiddle"> + <div class="typeContainerInner"> + <%= nodeType.getName() %> + </div> + </div> + </div> + + <div class="hidden"> + <nt:nodeTemplateRenderer + repositoryURL="${repositoryURL}" + client="${client}" + relationshipTypes="${relationshipTypes}" + nodeTypeQName="<%=new QName(nodeType.getTargetNamespace(), nodeType.getName())%>" + nodeType="<%=nodeType%>" /> + </div> + </div> + +<% + } +%> + +</div> + + +<script> + + //$("#palette").css("width","20px"); + //$("div.paletteEntry").hide(); + + $("#palette").click (function() { + showPalette(); + winery.events.fire(winery.events.name.command.UNSELECT_ALL_NODETEMPLATES); + }); + + function showPalette() { + // reset width to original CSS width + $("#palette").removeClass("shrunk"); + // show all palette entries + $("div.paletteEntry").show(); + $("#paletteLabel").hide(); + } + + function hidePalette() { + $("#palette").addClass("shrunk"); + // hide all palette entries + $("div.paletteEntry").hide(); + $("#paletteLabel").show(); + } + + $(function() { + $( "div.paletteEntry" ).draggable({ + cursor: "move", + cursorAt: { top: 40, left: 112 }, + helper: function( event ) { + var newObj = $(this).find("div.NodeTemplateShape").clone(); + newObj.removeClass("hidden"); + newObj.css("z-index", "2000"); + newObj.find ("div.endpointContainer").remove(); + + // Ensure that obj is appended to drawingarea and not to palette + // Consequence: the dragged object is always under the cursor and not paintet with an offset equal to the scrollheight + $("#drawingarea").append(newObj); + + return newObj; + }, + start: function( event, ui ) { + winery.events.fire(winery.events.name.command.UNSELECT_ALL_NODETEMPLATES); + // The palette is kept visible after a drag start, + // therefore no action + // hidePalette(); + }, + appendTo: '#drawingarea' + }); + + + $( "div#drawingarea" ).droppable({ + accept: function(d) { + if (d.hasClass("paletteEntry")) { + return true; + } + }, + drop: function( event, ui ) { + + var palEntry = ui.draggable; + var templateCode = palEntry.find("div.NodeTemplateShape").clone().wrap("<div></div>").parent().html(); + + var newObj = $(templateCode); + + newObj.removeClass("ui-draggable"); + newObj.removeClass("ui-droppable"); + newObj.removeClass("hidden"); + + // generate and set id + var type = newObj.find("div.type.nodetemplate").text(); + var id = type; + // we cannot use the id as the initial name, because we want to preserve special characters in the name, but not in the id. + var name = type; + + // quick hack to make id valid + // currently, only spaces and dots cause problems + id = id.replace(" ", "_"); + id = id.replace(".", "_"); + + if ($("#" + id).length != 0) { + var count = 2; + var idprefix = id + "_"; + do { + id = idprefix + count; + count++; + } while ($("#" + id).length != 0); + // also adjust name + name = name + "_" + count; + } + newObj.attr("id", id); + newObj.children("div.headerContainer").children("div.id").text(id); + + // initial name has been generated based on the id + newObj.children("div.headerContainer").children("div.name").text(name); + + // fix main.css -> #editorArea -> margin-top: 45px; + var top = Math.max(event.pageY-45, 0); + + // drag cursor is at 112/40 + // fix that + top = Math.max(top-40, 0); + var left = Math.max(event.pageX-112, 0); + + newObj.css("top", top); + newObj.css("left", left); + + newObj.addClass("selected"); + + // insert into sheet + newObj.appendTo( $( "div#drawingarea" ) ); + + // initialization works only for displayed objects + require(["winery-common-topologyrendering"], function(wct) { + wct.initNodeTemplate(newObj, true); + + // handle menus + winery.events.fire(winery.events.name.SELECTION_CHANGED); + }); + } + }) + + +}); + +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/nodetemplates/propertiesOfOneNodeTemplate.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/nodetemplates/propertiesOfOneNodeTemplate.tag new file mode 100644 index 0000000..238e34a --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/nodetemplates/propertiesOfOneNodeTemplate.tag @@ -0,0 +1,147 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - initial API and implementation and/or initial documentation + * Oliver Kopp - improvements to fit updated index.jsp + * Yves Schubert - switch to bootstrap 3 + *******************************************************************************/ +--%> + +<%@tag language="java" pageEncoding="UTF-8" description="Renders the properies of one node tempate on the right"%> + +<%@attribute name="repositoryURL" required="true" type="java.lang.String" description="The repository URL"%> + +<%@taglib prefix="ct" tagdir="/WEB-INF/tags/common" %> + + +<link rel="stylesheet" href="css/propertiesview.css" /> + +<div id="NTPropertiesView" class="propertiesView" style="display: none;"> + + <div id="nodeTemplateInformationSection"> + <%-- + If this is layouted strangely, maybe a <form> wrapper has to be added + Be aware that nested buttons then trigger a submission of the form (-> ct:spinnerwithinphty) + --%> + <fieldset> + <div class="form-group"> + <label for="nodetemplateid">Id</label> + <input id="nodetemplateid" disabled="disabled" class="form-control"></input> + </div> + <div class="form-group"> + <label for="nodetemplatename" class="control-label">Name</label> + <input id="nodetemplatename" name="name" class="form-control"/> + </div> + <div class="form-group"> + <label for="nodetemplateType">Type</label> + <%-- filled by fillInformationSection --%> + <a id="nodetemplateType" target="_blank" href="#" class="form-control"></a> + </div> + <ct:spinnerwithinphty min="0" width="10" changedfunction="minInstancesChanged" label="min" id="minInstances" /> + <ct:spinnerwithinphty min="1" width="10" changedfunction="maxInstancesChanged" label="max" id="maxInstances" withinphty="true" /> + </fieldset> + </div> + +</div> + +<script> + function minInstancesChanged(event, ui) { + var val; + if (ui === undefined) { + val = $("#minInstances").val(); + } else { + val = ui.value; + } + ntMin.html(val); + } + + function maxInstancesChanged(event, ui) { + var val; + if (ui === undefined) { + val = $("#maxInstances").val(); + } else { + val = ui.value; + } + ntMax.html(val); + } + + // the name input field of the properties section + var nameInput = $("#nodetemplatename"); + + // the min/max fields of the currently selected node template + var ntMin; + var ntMax; + + function fillInformationSection(nodeTemplate) { + require(["winery-support-common"], function(wsc) { + // currently doesn't help for a delayed update + //informationSection.slideDown(); + + $("#nodetemplateid").val(nodeTemplate.attr("id")); + + var headerContainer = nodeTemplate.children("div.headerContainer"); + + // copy name + var nameField = headerContainer.children("div.name"); + var name = nameField.text(); + nameInput.val(name); + + // copy type + var typeQName = headerContainer.children("span.typeQName").text(); + var href = wsc.makeNodeTypeURLFromQName("${repositoryURL}", typeQName); + var type = headerContainer.children("div.type").text(); + $("#nodetemplateType").attr("href", href).text(type); + + // we could use jQuery-typing, but it is not possible to replace key events there + nameInput.off("keyup"); + nameInput.on("keyup", function() { + nameField.text($(this).val()); + }); + + // handling of min and max + ntMin = nodeTemplate.children(".headerContainer").children(".minMaxInstances").children(".minInstances"); + $("#minInstances").val(ntMin.text()); + ntMax = nodeTemplate.children(".headerContainer").children(".minMaxInstances").children(".maxInstances"); + $("#maxInstances").val(ntMax.text()); + }); + } + + function showViewOnTheRight() { + $("#NTPropertiesView").fadeIn(); + } + + function hideViewOnTheRight() { + $("#NTPropertiesView").fadeOut(); + } + +$(function() { + winery.events.register( + winery.events.name.SELECTION_CHANGED, + function() { + // min/max instances do not lost focus if other shape is clicked + // workaround + if ($("#minInstances").is(":focus")) { + minInstancesChanged(); + } + if ($("#maxInstances").is(":focus")) { + maxInstancesChanged(); + } + var nodeTemplate = $("div.NodeTemplateShape.selected"); + var numSelected = nodeTemplate.length; + if (numSelected == 1) { + fillInformationSection(nodeTemplate); + showViewOnTheRight(); + } else { + hideViewOnTheRight(); + } + } + ); +}); +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/nodetemplates/reqscaps/addorupdatereqorcap.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/nodetemplates/reqscaps/addorupdatereqorcap.tag new file mode 100644 index 0000000..0ec6f5d --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/nodetemplates/reqscaps/addorupdatereqorcap.tag @@ -0,0 +1,248 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag description="Dialog to change a req or cap. Offers function showEditDiagFor${shortName}(id)" pageEncoding="UTF-8"%> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates" %> +<%@taglib prefix="o" tagdir="/WEB-INF/tags/common/orioneditor"%> +<%@taglib prefix="w" tagdir="/WEB-INF/tags"%> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions"%> + +<%@attribute name="headerLabel" required="true"%> +<%@attribute name="shortName" required="true" description="Used for diag id, function name suffix, Req|Cap"%> +<%@attribute name="requirementOrCapability" required="true" description="requirement|capability"%> +<%@attribute name="cssClassPrefix" required="true"%> +<%@attribute name="allTypes" required="true" type="java.util.Collection" description="Collection<QName> of all available types" %> +<%@attribute name="clazz" required="true" type="java.lang.Class" description="TRequirement.class or TCapability.class" %> +<%@attribute name="repositoryURL" required="true" %> + +<div class="modal fade" id="AddOrUpdate${shortName}Diag"> + <div class="modal-dialog"> + <div class="modal-content" style="width:660px;"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title"><span id="headerAddOrUpdate"></span> ${headerLabel}</h4> + </div> + <div class="modal-body"> + <form id="add${shortName}Form" enctype="multipart/form-data"> + <fieldset> + <w:idInput inputFieldId="${shortName}Id"/> + + <div class="form-group"> + <label for="${shortName}NameChooser" class="control-label">Definition Name:</label> + <input id="${shortName}NameChooser" class="form-control" type="text" required="required" /> + </div> + + <div class="form-group"> + <label for="${shortName}TypeDisplay" class="control-label">${shortName} Type:</label> + <input id="${shortName}TypeDisplay" class="form-control" type="text" required="required" disabled="disabled"/> + </div> + + <div id="${shortName}PropertiesContainer"> + </div> + </fieldset> + </form> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> + <button type="button" id="add${shortName}btn" class="btn btn-primary" onclick="addOrUpdate${shortName}(false);">Add</button> + <button type="button" id="delete${shortName}btn" class="btn btn-danger" onclick="deleteCurrent${shortName}();">Delete</button> + <button type="button" id="update${shortName}btn" class="btn btn-primary" onclick="addOrUpdate${shortName}(true);">Change</button> + </div> + </div> + </div> +</div> + +<script> + +/* + * The following variables are declared twice due to double inclusion of this .tag flie + * Due to JavaScript magic, it works nevertheless + */ +var selectedNodeTemplateForReqCapAddition; + +//global variable set by editPropertiesXML and read by save${shortName}Edits +var nodeTemplateEditedReqOrCap; + + +function deleteCurrent${shortName}() { + nodeTemplateEditedReqOrCap.remove(); + $("#AddOrUpdate${shortName}Diag").modal("hide"); +} + +function update${shortName}PropertiesContainerWithClone(propertiesContainerToClone) { + var clone = propertiesContainerToClone.clone(); + $("#${shortName}PropertiesContainer").empty().append(clone); + clone.find(".KVPropertyValue").editable({mode: "inline"}); +} + +function update${shortName}PropertiesFromSelectedType() { + var data = $("#${shortName}NameChooser").select2("data"); + var name = data.text; + var type = data.id; + + // fill in type + // TODO: use qname2href and store QName in data-qname for later consumption -- possibly qname2href should always store the qname in data-qname + $("#${shortName}TypeDisplay").val(type); + + // fill in properties (derived from type) + var propertiesContainer= $(".skelettonPropertyEditorFor${shortName} > span:contains('" + type + "')").parent().children("div"); + update${shortName}PropertiesContainerWithClone(propertiesContainer); +} + +$("#${shortName}NameChooser").on("change", function(e) { + update${shortName}PropertiesFromSelectedType(); +}); + +/** + * Called when a req/cap should be added or updated + * Update mode is triggered if reqOrCapIdtoUpdate is given + * + * @param nodeTemplateId the node template id to add a req/cap to. undefined in update mode + * @param reqOrCapIdtoUpdate + */ +function showAddOrUpdateDiagFor${shortName}(nodeTemplateId, reqOrCapIdToUpdate) { + var update = (typeof reqOrCapIdToUpdate !== "undefined"); + + if (update) { + nodeTemplateEditedReqOrCap = $("#" + reqOrCapIdToUpdate); + // in update mode, nodeTemplateId is not provided, we have to search for the right shape + selectedNodeTemplateForReqCapAddition = nodeTemplateEditedReqOrCap.closest(".NodeTemplateShape"); + } else { + selectedNodeTemplateForReqCapAddition = $("#" + nodeTemplateId); + } + + require(["winery-support-common"], function(wsc) { + var typeQName = selectedNodeTemplateForReqCapAddition.children("div.headerContainer").children("span.typeQName").text(); + var urlFragment = wsc.getURLFragmentOutOfFullQName(typeQName); + var url = "${repositoryURL}/nodetypes/" + urlFragment + "/${requirementOrCapability}definitions/"; + $.ajax({ + url: url, + dataType: "json" + }).fail(function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not fetch ${requirementOrCapability} definitions", jqXHR, errorThrown); + }).done(function(data) { + // now, we have all available requirement definitions + // we have to ask each of it for the type + // we use the type as key for the option and the name as displayed text + // select2 perfectly handles duplicate keys + + var select2Data = []; + + $.each(data, function(i,e) { + var rqDefURL = url + e + "/type"; + $.ajax({ + url: rqDefURL, + async: false, + dataType: "text" + }).fail(function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not fetch type for " + e, jqXHR, errorThrown); + }).done(function(data) { + var item = { + id: data, + text: e + }; + select2Data.push(item); + }); + }); + + $("#${shortName}NameChooser").select2({ + placeholder: "Select name", + data: select2Data + }); + + if (update) { + $("#add${shortName}btn").hide(); + $("#update${shortName}btn").show(); + $("#delete${shortName}btn").show(); + $("#headerAddOrUpdate").text("Change"); + + // collect existing data in variables + var id = nodeTemplateEditedReqOrCap.children(".id").text(); + var name = nodeTemplateEditedReqOrCap.children(".name").text(); + var type = nodeTemplateEditedReqOrCap.children(".type").children("a").data("qname"); + var propertiesContainer = nodeTemplateEditedReqOrCap.children(".propertiesContainer"); + + // update displays + + // id + $("#${shortName}Id").val(id); + + // name + // we use the type as key at NameChooser. We hope that there are no duplicates. Otherwise, update won't work. + $("#${shortName}NameChooser").select2("val", type); + // make consistency check + var data = $("#${shortName}NameChooser").select2("data"); + if (data == null) { + vShowError("type " + type + " could not be selected.") + } else if (name != (data.text)) { + vShowError("There are two names for different types. That case is not handled in the UI."); + } + + // type + $("#${shortName}TypeDisplay").val(type); + + // properties + update${shortName}PropertiesContainerWithClone(propertiesContainer); + } else { + $("#add${shortName}btn").show(); + $("#update${shortName}btn").hide(); + $("#delete${shortName}btn").hide(); + $("#headerAddOrUpdate").text("Add"); + + // QUICK HACK if dialog has been shown before -> show properties of selected type + if ($("#${shortName}NameChooser").select2("data") != null) { + update${shortName}PropertiesFromSelectedType(); + } + } + + $("#AddOrUpdate${shortName}Diag").modal("show"); + }); + }); +} + +/** + * Called at click on button "Add" or "Change" + */ +function addOrUpdate${shortName}(update) { + if (highlightRequiredFields()) { + vShowError("Please fill in all required fields"); + return; + } + require(["tmpl"], function(tmpl) { + // Generate skeletton div + var sel2data = $("#${shortName}NameChooser").select2("data"); + var data = { + id: $("#${shortName}Id").val(), + name: sel2data.text, + type: sel2data.id + } + // tmpl-${shortName} is defined in reqsorcaps.tag + var div = tmpl("tmpl-${shortName}", data); + + // Add the div to the node template + if (update) { + nodeTemplateEditedReqOrCap.replaceWith(div); + } else { + selectedNodeTemplateForReqCapAddition.children(".${cssClassPrefix}Container").children(".content").children(".addnewreqorcap").before(div); + } + + // Put properties at the right place + $("#toBeReplacedByProperties").replaceWith($("#${shortName}PropertiesContainer").children()); + + $("#AddOrUpdate${shortName}Diag").modal("hide"); + }); +} + +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/relationshiptemplates/propertiesOfOneRelationshipTemplate.tag b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/relationshiptemplates/propertiesOfOneRelationshipTemplate.tag new file mode 100644 index 0000000..8c63dca --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/tags/templates/relationshiptemplates/propertiesOfOneRelationshipTemplate.tag @@ -0,0 +1,179 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--%> +<%@tag language="java" pageEncoding="UTF-8" description="Renders the properies of one relationship tempate on the right"%> + +<%@attribute name="relationshipTypes" required="true" type="java.util.Collection" %> +<%@attribute name="repositoryURL" required="true" type="java.lang.String" description="The repository URL"%> + +<link rel="stylesheet" href="css/propertiesview.css" /> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@taglib prefix="props" tagdir="/WEB-INF/tags/common/templates" %> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions" %> + +<div id="RTPropertiesView" class="propertiesView" style="display: none;"> + + <div id="relationshipTemplateInformationSection"> + <fieldset> + <div class="form-group"> + <label for="relationshiptemplateid">Id</label> + <input id="relationshiptemplateid" disabled="disabled" class="form-control"></input> + </div> + <div class="form-group"> + <label for="relationshiptemplatename" class="control-label">Name</label> + <a href="#" id="relationshiptemplatename" data-title="Name" data-type="text" class="form-control"></a> + </div> + <div class="form-group"> + <label for="relationshipType">Type</label> + <%-- filled by showRTViewOnTheRight --%> + <a id="relationshipType" target="_blank" href="#" class="form-control"></a> + </div> + <div class="form-group"> + <label for="RTreq" class="control-label">Requirement</label> + <select id="RTreq" class="form-control"> + </select> + </div> + <div class="form-group"> + <label for="RTcap" class="control-label">Capability</label> + <select id="RTcap" class="form-control"> + </select> + </div> + </fieldset> + </div> + +</div> + +<script> + + var currentlySelectedConn = null; + + /** + * Fills the requirement and capabilities dropdowns with the available reqs and caps (which are defined at the source/target node template) + * + * @param conn the connection itself + * @param dataField = "req"|"cap" + * @param sourceDivClass = requirementsContainer | capabilitiesContainer + */ + function fillReqOrCap(conn, dataField, nodetemplateId, sourceDivClass, targetSelect) { + var nt = $("#" + nodetemplateId); + var reqsOrCaps = nt.children("." + sourceDivClass).children(".content").children(".reqorcap"); + var connReqCap = winery.connections[conn.id][dataField]; + + targetSelect.empty(); + + var optData = { + value: "__NONE__", + text: "(none)" + }; + if (!connReqCap) { + selected: true + } + require(["tmpl"], function(tmpl) { + var newOption = tmpl("tmpl-option", optData); + targetSelect.append(newOption); + + reqsOrCaps.each(function(i,e) { + optData.value = $(e).children(".id").children("span.id").text(); + optData.text = $(e).children(".name").children("span.name").text(); + optData.selected = (optData.value == connReqCap); + newOption = tmpl("tmpl-option", optData); + targetSelect.append(newOption); + }); + }); + + targetSelect.off("change"); + targetSelect.on("change", function(e) { + var val = targetSelect.val(); + if (val == "__NONE__") { + delete(winery.connections[conn.id][dataField]); + } else { + winery.connections[conn.id][dataField] = val; + } + }); + } + + function fillType(nsAndLocalName) { + require(["winery-support-common"], function(wsc) { + var href = wsc.makeRelationshipTypeURLFromNSAndLocalName("${repositoryURL}", nsAndLocalName); + // localname is always the name of the relationship type because the specification requires a "name" attribute only and does not foresee an "id" attribute + $("#relationshipType").attr("href", href).text(nsAndLocalName.localname); + }) + } + + function displayProperties(connData) { + $("#RTPropertiesView").append(connData.propertiesContainer); + } + + /** + * @param conn the jsPlumb connection + */ + function showRTViewOnTheRight(conn) { + currentlySelectedConn = conn; + + $("#RTPropertiesView").fadeIn(); + + $("#relationshiptemplateid").val(winery.connections[conn.id].id); + $("#relationshiptemplatename").editable('setValue', winery.connections[conn.id].name); + fillReqOrCap(conn, "req", conn.sourceId, "requirementsContainer", $("#RTreq")); + fillReqOrCap(conn, "cap", conn.targetId, "capabilitiesContainer", $("#RTcap")); + fillType(winery.connections[conn.id].nsAndLocalName); + displayProperties(winery.connections[conn.id]); + } + + function hideRTViewOnTheRight() { + if (currentlySelectedConn == null) { + // nothing to do if no relationship template is selected + return; + } + + $("#RTPropertiesView").fadeOut(); + + // user will see some flickering here, but we don't want to set timers -> could lead to race conditions + $("#skelettonContainerForRelationshipTemplates").append(winery.connections[currentlySelectedConn.id].propertiesContainer); + currentlySelectedConn = null; + } + + function storeUpdatedName(newName) { + currentlySelectedConn.name = newName; + } + + $(function() { + $("#relationshiptemplatename").editable({ + success: function(response, newValue) { + currentlySelectedConn.name = newValue; + } + }); + }); + + function unselectAllConnections() { + jsPlumb.select().each(function(connection) { + connection.removeType("selected"); + }); + } + + winery.events.register( + winery.events.name.SELECTION_CHANGED, + function() { + var nodeTemplate = $("div.NodeTemplateShape.selected"); + var numSelected = nodeTemplate.length; + if (numSelected != 0) { + // if node templates are selected, no RT properties should be shown + hideRTViewOnTheRight(); + + unselectAllConnections(); + } + } + ); + +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/web.xml b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..37b6dc9 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +--> +<web-app xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + id="winery-topologymodeler" + version="3.0"> + <display-name>Winery Topology Modeler</display-name> +</web-app> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/palette.css b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/palette.css new file mode 100644 index 0000000..e67a715 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/palette.css @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - initial API and implementation and/or initial documentation + * Oliver Kopp - improvements + *******************************************************************************/ + +#palette { + min-height: 180px; + background: rgb(248, 248, 248); + width: 230px; + box-shadow: 2px 2px 7px rgb(156, 156, 156); + padding: 10px; + border: 1px solid #aeaeae; + position: fixed; + top: 50px; + left: 0px; + z-index: 100; + cursor: default; + border-radius: 0px 9px 9px 0px; + background-image: -ms-linear-gradient(left, #FFFFFF 0%, rgb(237, 242, 247) 100%); + background-image: -moz-linear-gradient(left, #FFFFFF 0%, rgb(237, 242, 247) 100%); + background-image: -o-linear-gradient(left, #FFFFFF 0%, rgb(237, 242, 247) 100%); + background-image: -webkit-gradient(linear, left, right, color-stop(0, #FFFFFF), color-stop(1, rgb(237, 242, 247))); + background-image: -webkit-linear-gradient(left, #FFFFFF 0%, rgb(237, 242, 247) 100%); + background-image: linear-gradient(to right, #FFFFFF 0%, rgb(237, 242, 247) 100%); + + overflow-x: hidden; + overflow-y: visible; + max-height: 90%; + + /* fix for bootstrap.css, which sets that to "border-box" */ + box-sizing: content-box; + -webkit-box-sizing: content-box; +} + +#palette.shrunk { + width: 35px; + padding: 0px; + padding-left: 9px; +} + +#paletteLabel { + width: 0px; + background-color: blue; + top: 95px; + position: relative; + transform: rotate(270deg); + -o-transform: rotate(270deg); + -moz-transform: rotate(270deg); + -ms-transform: rotate(270deg); + -webkit-transform: rotate(270deg); + display: none; + left: 8px; + font-size: 12px; + color: rgb(143, 151, 170); +} + +div.iconContainer { + width: 45px; + margin-left: 5px; + float: left; + height: 20px; + margin-top: 4px; +} + + +div.paletteEntry { + height: 28px; + border-bottom: 1px solid rgb(200, 214, 228); + margin: 0px; + float: left; + width: 230px; +} + +div.paletteEntry:hover { + background: rgb(237, 242, 247); +} + + +div.paletteEntry > div.iconContainer > img.icon { + height: 20px; + vertical-align: top; +} + + +div.paletteEntry > div.typeContainer { + margin: 4px 0px; + height: 20px; + display: table; + width: 180px; + font-size: 11px; + line-height: 20px; +} + +div.paletteEntry > div.typeContainer > div.typeContainerMiddle { + display: table-cell; + vertical-align: middle; + width: 100%; + position: static; +} + +div.paletteEntry > div.typeContainer > div.typeContainerMiddle > div.typeContainerInner { +} + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/propertiesview.css b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/propertiesview.css new file mode 100644 index 0000000..3ff0d3b --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/propertiesview.css @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - initial API and implementation and/or initial documentation + * Oliver Kopp - improvements + *******************************************************************************/ + +div.propertiesView { + width: 200px; + position: fixed; + top: 57px; + right: -1px; + background: #eaeaea; + z-index: 600; + background-image: linear-gradient(to left, #FFFFFF 0%, rgb(237, 243, 247) 100%); + border-radius: 9px 0px 0px 9px; + border: 1px solid #aeaeae; + padding: 14px; + box-shadow: -1px 2px 7px rgb(156, 156, 156); + font-size: 80%; +} + +#propertiesSection { + padding: 10px; +} + +#propertiesSection div.content { + background: rgb(244, 247, 250); +} + +#daSection { + background: #eaeaea; + padding: 10px; +} + +#daSection div.content { + background: rgb(244, 247, 250); +} + +#nodeTemplateInformationSection > div.control-group > label.control-label { + width: 37px; +} + +#nodeTemplateInformationSection > div.control-group > input { + width: 100px; +} + + +/* relationship templates only; adapted from topologytemplatecontent.css */ + +#RTPropertiesView > div.propertiesContainer > div.header { + font-weight: bold +} + +#RTPropertiesView > div.propertiesContainer > div.header > a { + float: right; + height: 6px; +} + +#RTPropertiesView > div.propertiesContainer > div.content > span.namespace { + display: none; +} + +#RTPropertiesView > div.propertiesContainer > div.content > span.elementName { + display: none; +} + +#RTPropertiesView > div.propertiesContainer > div.content table { + float: left; +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/topologymodeler.css b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/topologymodeler.css new file mode 100644 index 0000000..18f83ea --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/topologymodeler.css @@ -0,0 +1,286 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - initial API and implementation and/or initial documentation + * Oliver Kopp - improvements + * Yves Schubert - switch to bootstrap 3 + *******************************************************************************/ + +/* used if elements should be hidden forever. Cannot be undone with $(...).show() */ +.hidden { + display: none; +} + +.fileupload { + display: none; +} + +.overflowhidden { + overflow: hidden; + text-overflow: ellipsis; +} + + +body { + background-color: white; + margin: 0px; +} + +#loading { + position: absolute; + top: 0px; + left: 0px; + height: 100%; + width: 100%; z-index: 200; + background-color: white; + z-index: 2000; +} + +#topbar { + padding: 5px; + width: 100%; + position: fixed; + top: 0px; + z-index: 600; + background-color: white; +} + +/* override jquery-ui.css */ +.ui-widget { + font-size: 1.0em; + font-family: Arial, Helvetica, sans-serif; +} + +a:hover { + color:#1b911b; + background-color:#f0f0f0; +} + +#drawingarea { + height: 80em; + position: relative; +} + +#drawingarea.editview { + background: rgb(240, 246, 255); + background-image: -ms-linear-gradient(top, #FFFFFF 0%, rgb(243, 243, 243) 100%); + background-image: -moz-linear-gradient(top, #FFFFFF 0%, rgb(243, 243, 243) 100%); + background-image: -o-linear-gradient(top, #FFFFFF 0%, rgb(243, 243, 243) 100%); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #FFFFFF), color-stop(1, rgb(243, 243, 243))); + background-image: -webkit-linear-gradient(top, #FFFFFF 0%, rgb(243, 243, 243) 100%); + background-image: linear-gradient(to bottom, #FFFFFF 0%, rgb(243, 243, 243) 100%); +} + +#drawingarea.printview { +} + +/* currently not used */ +div.focusedElement { + border: 3px dotted rgb(0, 152, 255); + + /* IE10 Consumer Preview */ + background-image: -ms-linear-gradient(top, #FFFFFF 0%, rgb(153, 243, 255) 100%); + + /* Mozilla Firefox */ + background-image: -moz-linear-gradient(top, #FFFFFF 0%, rgb(153, 243, 255) 100%); + + /* Opera */ + background-image: -o-linear-gradient(top, #FFFFFF 0%, rgb(153, 243, 255) 100%); + + /* Webkit (Safari/Chrome 10) */ + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #FFFFFF), color-stop(1, rgb(153, 243, 255))); + + /* Webkit (Chrome 11+) */ + background-image: -webkit-linear-gradient(top, #FFFFFF 0%, rgb(153, 243, 255) 100%); + + /* W3C Markup, IE10 Release Preview */ + background-image: linear-gradient(to bottom, #FFFFFF 0%, rgb(153, 243, 255) 100%); +} + + +div.NodeTemplateShape.selected { + background: rgb(216, 238, 255); + border: 2px solid rgb(255, 127, 26); + + + /* IE10 Consumer Preview */ + background-image: -ms-linear-gradient(top, #FFFFFF 0%, #FFD391 100%); + + /* Mozilla Firefox */ + background-image: -moz-linear-gradient(top, #FFFFFF 0%, #FFD391 100%); + + /* Opera */ + background-image: -o-linear-gradient(top, #FFFFFF 0%, #FFD391 100%); + + /* Webkit (Safari/Chrome 10) */ + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #FFFFFF), color-stop(1, #FFD391)); + + /* Webkit (Chrome 11+) */ + background-image: -webkit-linear-gradient(top, #FFFFFF 0%, #FFD391 100%); + + /* W3C Markup, IE10 Release Preview */ + background-image: linear-gradient(to bottom, #FFFFFF 0%, #FFD391 100%); +} + +div.content { + overflow: hidden; + width: 100%; +} + +.renderMode.ui-draggable { + display:none; +} + +div.menu { + display:none; +} + +._jsPlumb_overlay { + z-index: 5000; +} + +/* overriding dimensions.css to overcome problems with non-standard fonts and breaking cells */ +.u-size3of5, +.u-size6of10 { + width: 59%; +} +.u-size4of5 { + width: 79%; +} + +.breakword { + overflow-wrap: break-word; +} + +a.topbutton { + margin-right: 10px; +} + +#selectionbox { + position: absolute; + border-style: dashed; + border-width: 3px; + border-color: black; + background-color: cyan; + opacity: 0.15; + z-index: 30; + display: none; +} + +#editorArea { + width: 100%; + height: 100%; + margin-top: 45px; +} + +#nodeTemplateInformationSection > label { + display: inline-block; + width: 47px; +} + +.form-horizontal .controls > span { + margin-top: 5px; + display: inline-block; +} + +/* reset editable style */ +.editable-click { + border-bottom: none; +} + +a.editable-click { + border-bottom: none; +} + +a.editable-click:hover { + border-bottom: none; +} + + +/* adding a req/cap */ + +div.modal-body > form.addReqForm > div.propertiesContainer { + border: 1px solid #aeaeae; + background: #ffffff; + margin: 7px; + width: 198px; + border-radius: 7px; + padding: 5px; + float: left; + overflow-x: hidden; + display: none; +} + +div.modal-body > form.addReqForm > div.propertiesContainer > div.header { + background: rgb(241, 241, 241); + border-bottom: 1px solid #aeaeae; + float: left; + width: 198px; + padding: 5px; + margin-top: -5px; + margin-left: -5px; + margin-bottom: 6px; +} + +div.modal-body > form.addReqForm > div.propertiesContainer > div.header > a { + float: right; + height: 6px; +} + +div.modal-body > form.addReqForm > div.propertiesContainer > div.content > span.namespace { + display: none; +} + +div.modal-body > form.addReqForm > div.propertiesContainer > div.content > span.elementName { + display: none; +} + +div.modal-body > form.addReqForm > div.propertiesContainer > div.content table { + float: left; +} + +div.modal-body > form.addCapForm > div.propertiesContainer { + border: 1px solid #aeaeae; + background: #ffffff; + margin: 7px; + width: 198px; + border-radius: 7px; + padding: 5px; + float: left; + overflow-x: hidden; + display: none; +} + +div.modal-body > form.addCapForm > div.propertiesContainer > div.header { + background: rgb(241, 241, 241); + border-bottom: 1px solid #aeaeae; + float: left; + width: 198px; + padding: 5px; + margin-top: -5px; + margin-left: -5px; + margin-bottom: 6px; +} + +div.modal-body > form.addCapForm > div.propertiesContainer > div.header > a { + float: right; + height: 6px; +} + +div.modal-body > form.addCapForm > div.propertiesContainer > div.content > span.namespace { + display: none; +} + +div.modal-body > form.addCapForm > div.propertiesContainer > div.content > span.elementName { + display: none; +} + +div.modal-body > form.addCapForm > div.propertiesContainer > div.content table { + float: left; +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/topologytemplatecontent.css b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/topologytemplatecontent.css new file mode 100644 index 0000000..0c7eea4 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/topologytemplatecontent.css @@ -0,0 +1,421 @@ +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - initial API and implementation and/or initial documentation + * Oliver Kopp - improvements + *******************************************************************************/ + +/** This CSS is shared between the Winery Repository and the Winery Topology Modeler **/ + +div.NodeTemplateShape { + font-family: arial, verdana; + font-size: 12px; + + border: 1px solid black; + border: 2px solid rgb(112, 152, 179); + border-radius: 12px; + width: 225px; + z-index:20; + position:absolute; + box-shadow: 5px 5px 17px #aaa; + background: #ffffff; + cursor: move; + + /* IE10 Consumer Preview */ + background-image: -ms-linear-gradient(top, #FFFFFF 0%, #EBF2F7 100%); + + /* Mozilla Firefox */ + background-image: -moz-linear-gradient(top, #FFFFFF 0%, #EBF2F7 100%); + + /* Opera */ + background-image: -o-linear-gradient(top, #FFFFFF 0%, #EBF2F7 100%); + + /* Webkit (Safari/Chrome 10) */ + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #FFFFFF), color-stop(1, #EBF2F7)); + + /* Webkit (Chrome 11+) */ + background-image: -webkit-linear-gradient(top, #FFFFFF 0%, #EBF2F7 100%); + + /* W3C Markup, IE10 Release Preview */ + background-image: linear-gradient(to bottom, #FFFFFF 0%, #EBF2F7 100%); +} + +div.NodeTemplateShape > div.headerContainer { + height: 65px; +} + +div.NodeTemplateShape div.minMaxInstances { +} + +div.NodeTemplateShape div.minMaxInstances span.minInstances { + +} + +div.NodeTemplateShape div.minMaxInstances span.maxInstances { + +} + +div.NodeTemplateShape > .headerContainer > img.icon { + float: left; + height: 55px; + margin: 10px 0px 10px 10px; +} +div.NodeTemplateShape > .headerContainer > div.id { + position: absolute; + left: 90px; + top: 0px; + height: 16px; + width: 130px; + text-overflow: ellipsis; + overflow: hidden; + text-decoration: underline; + display: none; +} +div.NodeTemplateShape > .headerContainer > div.name { + position: absolute; + left: 90px; + top: 20px; + height: 16px; + width: 130px; + text-overflow: ellipsis; + overflow: hidden; +} +div.NodeTemplateShape > .headerContainer > div.type { + position: absolute; + left: 90px; + top: 40px; + height: 16px; + width: 130px; + text-overflow: ellipsis; + overflow: hidden; +} +div.NodeTemplateShape > .headerContainer > div.type:before { + content: "("; +} +div.NodeTemplateShape > .headerContainer > div.type:after { + content: ")"; +} + +img.createAnnotation { + width: 20px; + position: absolute; + top: 5px; + right: 5px; +} + +a.KVPropertyValue { + overflow: hidden; + text-overflow: ellipsis; +} + +div.NodeTemplateShape > div.deploymentArtifactsContainer { + border: 1px solid #aeaeae; + background: #ffffff; + margin: 7px; + width: 198px; + border-radius: 7px; + padding: 5px; + float: left; + overflow-x: hidden; + display: none; +} + +div.NodeTemplateShape > div.deploymentArtifactsContainer > div.header { + border-bottom: 1px solid #aeaeae; + float: left; + width: 198px; + padding: 5px; + margin-top: -5px; + margin-left: -5px; + margin-bottom: 6px; + background-image: -ms-linear-gradient(top, #FFFFFF 0%, rgb(209, 209, 209) 100%); + background-image: -moz-linear-gradient(top, #FFFFFF 0%, rgb(209, 209, 209) 100%); + background-image: -o-linear-gradient(top, #FFFFFF 0%, rgb(209, 209, 209) 100%); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #FFFFFF), color-stop(1, rgb(209, 209, 209))); + background-image: -webkit-linear-gradient(top, #FFFFFF 0%, rgb(209, 209, 209) 100%); + background-image: linear-gradient(to bottom, #FFFFFF 0%, rgb(209, 209, 209) 100%); +} + +div.NodeTemplateShape > div.deploymentArtifactsContainer > div.content > div.deploymentArtifact { + background-color: #FFDADA; + height: 20px; +} + +/* indicates editing possibility */ +div.NodeTemplateShape > div.deploymentArtifactsContainer > div.content > div.deploymentArtifact:hover { + background-color: lightblue; + cursor: pointer; +} + +div.NodeTemplateShape > div.deploymentArtifactsContainer > div.content > div.deploymentArtifact > div { + height: 20px; +} + +div.NodeTemplateShape > div.deploymentArtifactsContainer > div.content > div.deploymentArtifact:first-child { + border-top: 0px; +} + +div.NodeTemplateShape > div.deploymentArtifactsContainer > div.content > div.addDA { + display: none; +} + +div.NodeTemplateShape > div.deploymentArtifactsContainer > div.content > div.addnewartifacttemplate { + display: none; + background-color: Silver; + text-align: center; +} + +div.NodeTemplateShape > div.propertiesContainer { + border: 1px solid #aeaeae; + background: #ffffff; + margin: 7px; + width: 198px; + border-radius: 7px; + padding: 5px; + float: left; + overflow-x: hidden; + display: none; +} + +div.NodeTemplateShape > div.propertiesContainer > div.header { + background: rgb(241, 241, 241); + border-bottom: 1px solid #aeaeae; + float: left; + width: 198px; + padding: 5px; + margin-top: -5px; + margin-left: -5px; + margin-bottom: 6px; +} + +div.NodeTemplateShape > div.propertiesContainer > div.header > a { + float: right; + height: 6px; +} + +div.NodeTemplateShape > div.propertiesContainer > div.content > span.namespace { + display: none; +} + +div.NodeTemplateShape > div.propertiesContainer > div.content > span.elementName { + display: none; +} + +div.NodeTemplateShape > div.propertiesContainer > div.content table { + float: left; +} + +div.NodeTemplateShape div.reqorcap { + cursor: pointer; +} + +div.NodeTemplateShape div.reqorcap.id { +} + +div.NodeTemplateShape div.reqorcap.name { + overflow: hidden; + text-overflow: ellipsis; +} + +div.NodeTemplateShape div.reqorcap.type { + overflow: hidden; + text-overflow: ellipsis; +} + +/* indicates editing possibility */ +div.NodeTemplateShape div.reqorcap:hover { + background-color: lightgray; +} + +div.NodeTemplateShape > div.requirementsContainer { + border: 1px solid #aeaeae; + background: #ffffff; + margin: 7px; + width: 198px; + border-radius: 7px; + padding: 5px; + float: left; + overflow-x: hidden; + display: none; +} + +div.NodeTemplateShape > div.requirementsContainer > div.header { + background: rgb(241, 241, 241); + border-bottom: 1px solid #aeaeae; + float: left; + width: 198px; + padding: 5px; + margin-top: -5px; + margin-left: -5px; + margin-bottom: 6px; +} + +div.NodeTemplateShape > div.requirementsContainer > div.content > div.reqorcap > div.propertiesContainer { + display: none; +} + + +div.NodeTemplateShape > div.capabilitiesContainer { + border: 1px solid #aeaeae; + background: #ffffff; + margin: 7px; + width: 198px; + border-radius: 7px; + padding: 5px; + float: left; + overflow-x: hidden; + display: none; +} + +div.NodeTemplateShape > div.capabilitiesContainer > div.header { + background: rgb(241, 241, 241); + border-bottom: 1px solid #aeaeae; + float: left; + width: 198px; + padding: 5px; + margin-top: -5px; + margin-left: -5px; + margin-bottom: 6px; +} + +div.NodeTemplateShape > div.capabilitiesContainer > div.content > div.reqorcap > div.propertiesContainer { + display: none; +} + + +/** Policies **/ + +div.NodeTemplateShape > div.policiesContainer { + border: 1px solid #aeaeae; + background: #ffffff; + margin: 7px; + width: 198px; + border-radius: 7px; + padding: 5px; + float: left; + overflow-x: hidden; + display: none; +} + +div.NodeTemplateShape > div.policiesContainer > div.header { + background: rgb(241, 241, 241); + border-bottom: 1px solid #aeaeae; + float: left; + width: 198px; + padding: 5px; + margin-top: -5px; + margin-left: -5px; + margin-bottom: 6px; +} + +/* indicates editing possibility */ +div.NodeTemplateShape > div.policiesContainer > div.content > div.policy:hover { + background-color: lightgray; +} + +div.NodeTemplateShape > div.policiesContainer > div.content > div.policy > div { + overflow: hidden; + text-overflow: ellipsis; +} + +div.NodeTemplateShape > div.policiesContainer > div.content > div.policy > span { + display: none; +} + +div.NodeTemplateShape > div.policiesContainer > div.content > div.policy > textarea.policy_xml { + display: none; +} + + + +div.connectorBox { + height: 15px; + width: 15px; + float: left; +} + +div.connectorLabel { + height: 15px; + width: 125px; + overflow: hidden; + margin-left: 20px; + white-space: nowrap; + line-height: 16px; + overflow: hidden; + text-overflow: ellipsis; +} + +div.connectorEndpoint { + width: 140px; + cursor: pointer; +} + +div.connectorEndpoint:hover { + background: rgb(237, 242, 247); +} + +div.endpointContainer { + background: #ffffff; + box-shadow: 2px 2px 19px #aaa; + border: 1px solid #aeaeae; + width: 150px; + position: absolute; + left: 212px; + padding: 5px; + z-index: 20; + display: none; +} + +._jsPlumb_connector { + z-index: 15; +} + +div#patternArea { + position: absolute; + z-index: 10000; + right: 0px; + top: 0px; + height: 100%; + width: 500px; + background: rgb(250, 250, 250); + padding: 5px; +} + +div.patternSuggestionContainer { + border: 2px solid #aeaeae; + padding: 5px 3px; + margin-bottom: 5px; +} + +div.patternSuggestionContainer.focusedElement { + +} + +.pointer { + cursor: pointer; +} + +div.relationshipTypeLabel { + /* z-index of arrow is 14, therefore we use 15 */ + z-index: 15; + + cursor: default; + font-family: arial, verdana; + font-size: 12px; +} + +.unselectable { + /* disable text selection - source: http://stackoverflow.com/a/4407335/873282 */ + user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -webkit-touch-callout: none; + -webkit-user-select: none; +} diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/winery-common.css b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/winery-common.css new file mode 100644 index 0000000..b6d2251 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/css/winery-common.css @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - initial API and implementation and/or initial documentation + * Oliver Kopp - improvements + * Yves Schubert - switch to bootstrap 3 + *******************************************************************************/ + +/** This CSS is shared between the Winery Repository and the Winery Topology Modeler **/ + +.informationbox { + position: absolute; + text-align: center; + top: 0; + left: 0; + width: 100%; + height: 0; + z-index: 99; +} + +.informationbox > .alert { + background: #fff1a8; + border: 1px solid #999; + border-top: 0; + border-radius: 0 0 3px 3px; + display: inline-block; + line-height: 21px; + padding: 0 12px; +} + +/* when hovering over the text, the cursor should stay as is */ +.ui-pnotify { + cursor: default; +} + +/* change pnotify's container to be always at the top of the window */ +div.ui-pnotify-history-container { + position: fixed; +} + +/* enable pnotify notifications to wrap correctly */ +.ui-pnotify-text { + overflow: hidden; + overflow-wrap: break-word; +} + +.button { + cursor: pointer; +} + +td.editable { + cursor: text; +} + +td { + cursor: default; +} + +.input-xxlarge { + width: 510px; +} + +.spinner { + text-align: right; +} + +/* enables stacked modal dialogs */ +div.z1051 { + z-index: 1051; +} + +/* used for showing the full notification at PNotify */ +div.z1060 { + z-index: 1060; +} + + +/* fixes bootstrap margin problem at horizontal form */ +.form-horizontal > div.control-group > label.control-label { + margin-right: 10px; +} + +/* used by artifactcreationdialog.tag */ +div.unknown { + color: gray; +} + +div.unknown:before { + content: '?'; +} + +div.invalid { + color: red; +} + +div#artifactTemplateNameIsValid { + height: 19px; +} + +div.valid { + color: green; +} + +#diagmessagemsg { + text-overflow: ellipsis; + overflow: hidden; +} + +textarea.properties_xml { + display: none +} + +/** properties **/ + +span.properties_element { + display: none; +} + +span.properties_type { + display: none +} + +div.form-group-grouping { + background: #F0F0F0; + padding: 15px; + margin-top: 10px; + margin-bottom: 10px; +} + +div.orionxmleditordiv { + height: 300px; +}
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/favicon.png b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/favicon.png Binary files differnew file mode 100644 index 0000000..25484fe --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/favicon.png diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/images/xml.png b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/images/xml.png Binary files differnew file mode 100644 index 0000000..eb46323 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/images/xml.png diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/index.jsp b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/index.jsp new file mode 100644 index 0000000..2a582ba --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/index.jsp @@ -0,0 +1,1490 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Uwe Breitenbücher - initial API and implementation and/or initial documentation + * Oliver Kopp - integration with the repository, adapted to TOSCA v1.0 + * Yves Schubert - switch to bootstrap 3 + *******************************************************************************/ +--%> + +<%@page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> +<%@page buffer="none" %> +<%@page import="java.util.Collection"%> +<%@page import="java.util.List"%> +<%@page import="java.util.LinkedList"%> +<%@page import="java.util.Map"%> +<%@page import="java.util.SortedSet"%> +<%@page import="javax.xml.namespace.QName"%> +<%@page import="org.apache.commons.lang3.StringUtils"%> +<%@page import="org.eclipse.winery.model.tosca.TArtifactTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TArtifactType"%> +<%@page import="org.eclipse.winery.model.tosca.TCapability"%> +<%@page import="org.eclipse.winery.model.tosca.TCapabilityType"%> +<%@page import="org.eclipse.winery.model.tosca.TEntityTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TTopologyTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TNodeTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TNodeType"%> +<%@page import="org.eclipse.winery.model.tosca.TPolicyType"%> +<%@page import="org.eclipse.winery.model.tosca.TRelationshipType"%> +<%@page import="org.eclipse.winery.model.tosca.TRelationshipTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TRelationshipTemplate.SourceElement"%> +<%@page import="org.eclipse.winery.model.tosca.TRelationshipTemplate.TargetElement"%> +<%@page import="org.eclipse.winery.model.tosca.TRequirement"%> +<%@page import="org.eclipse.winery.model.tosca.TRequirementType"%> +<%@page import="org.eclipse.winery.common.constants.Namespaces" %> +<%@page import="org.eclipse.winery.common.ids.definitions.ArtifactTemplateId"%> +<%@page import="org.eclipse.winery.common.ids.definitions.ServiceTemplateId" %> +<%@page import="org.eclipse.winery.common.interfaces.QNameWithName"%> +<%@page import="org.eclipse.winery.common.constants.QNames" %> +<%@page import="org.eclipse.winery.common.ModelUtilities"%> +<%@page import="org.eclipse.winery.common.Util"%> +<%@page import="org.eclipse.winery.repository.client.WineryRepositoryClientFactory"%> +<%@page import="org.eclipse.winery.repository.client.IWineryRepositoryClient"%> +<%@page import="org.eclipse.winery.repository.client.WineryRepositoryClient"%> +<%@page import="org.eclipse.winery.topologymodeler.WineryUtil"%> +<%@page import="com.sun.jersey.api.client.WebResource"%> +<%@page import="com.sun.jersey.api.client.Client"%> +<%@page import="com.sun.jersey.api.client.ClientResponse"%> +<%@page import="com.sun.jersey.api.client.config.ClientConfig"%> +<%@page import="com.sun.jersey.api.client.config.DefaultClientConfig"%> + +<%-- nc.. = non-common .. --%> +<%@taglib prefix="ncnt" tagdir="/WEB-INF/tags/templates/nodetemplates" %> +<%@taglib prefix="ncrt" tagdir="/WEB-INF/tags/templates/relationshiptemplates" %> +<%@taglib prefix="tntrq" tagdir="/WEB-INF/tags/templates/nodetemplates/reqscaps" %> + +<%@taglib prefix="ct" tagdir="/WEB-INF/tags/common" %> +<%@taglib prefix="tmpl" tagdir="/WEB-INF/tags/common/templates" %> +<%@taglib prefix="nt" tagdir="/WEB-INF/tags/common/templates/nodetemplates" %> +<%@taglib prefix="ntrq" tagdir="/WEB-INF/tags/common/templates/nodetemplates/reqscaps" %> +<%@taglib prefix="pol" tagdir="/WEB-INF/tags/common/policies" %> + +<%@taglib prefix="t" tagdir="/WEB-INF/tags" %> + +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@taglib prefix="wc" uri="http://www.eclipse.org/winery/functions" %> + +<%@taglib prefix="tc" tagdir="/WEB-INF/tags/common/topologycompletion"%> + +<% + String repositoryURL = request.getParameter("repositoryURL"); + if (StringUtils.isEmpty(repositoryURL)) { + repositoryURL = "http://localhost:8080/winery"; + } else if (repositoryURL.endsWith("/")) { + repositoryURL = repositoryURL.substring(0, repositoryURL.length()-1); + } + + String ns = request.getParameter("ns"); + if (StringUtils.isEmpty(ns)) { +%> + A namespace has to be provided by using the query parameter “ns.” Please start the modeler using the <a href="<%=repositoryURL%>">repository</a>. +<% + return; + } + + String id = request.getParameter("id"); + if (StringUtils.isEmpty(id)) { +%> + An id has to be provided by using the query parameter “id.” Please start the modeler using the <a href="<%=repositoryURL%>">repository</a>. +<% + return; + } + + // initialize client dependend on useproxy URL parameter + IWineryRepositoryClient client; + if (request.getParameterMap().containsKey("useproxy")) { + // debugging - using fiddler: + client = new WineryRepositoryClient(true); + System.out.println("Using a proxy..."); + } else { + // production: + client = WineryRepositoryClientFactory.getWineryRepositoryClient(); + } + + client.addRepository(repositoryURL); + + if (!client.primaryRepositoryAvailable()) { +%> + The repository is not available. +<% + return; + } + + QName serviceTemplateQName = new QName(ns, id); + TTopologyTemplate topologyTemplate = client.getTopologyTemplate(serviceTemplateQName); + if (topologyTemplate == null) { +%> + Something went wrong in the repository: topology template not found. +<% + return; + } + + String topologyTemplateURL = repositoryURL + "/servicetemplates/" + Util.DoubleURLencode(serviceTemplateQName) + "/topologytemplate/"; + + String serviceTemplateName = client.getName(new ServiceTemplateId(serviceTemplateQName)); + + Collection<TRelationshipType> relationshipTypes = client.getAllTypes(TRelationshipType.class); +%> +<!DOCTYPE html> +<html> +<head> + <title>Winery Topologymodeler – <%= serviceTemplateName %></title> + <meta http-equiv="content-type" content="text/html;charset=utf-8" /> + + <link rel="icon" href="favicon.png" type="image/png"> + + <link rel="stylesheet" href="components/bootstrap/dist/css/bootstrap.css" /> + <link rel="stylesheet" href="components/bootstrap/dist/css/bootstrap-theme.css" /> + <link rel="stylesheet" href="components/bootstrap-spinedit/css/bootstrap-spinedit.css" /> + + <link rel="stylesheet" href="components/blueimp-file-upload/css/jquery.fileupload.css" /> + <link rel="stylesheet" href="components/blueimp-file-upload/css/jquery.fileupload-ui.css" /> + + <link type="text/css" href="components/pnotify/jquery.pnotify.default.css" media="all" rel="stylesheet" /> + <link type="text/css" href="components/pnotify/jquery.pnotify.default.icons.css" media="all" rel="stylesheet" /> + + <!-- select2 --> + <link type="text/css" href="components/select2/select2.css" media="all" rel="stylesheet" /> + <link type="text/css" href="components/select2/select2-bootstrap.css" media="all" rel="stylesheet" /> + + <!-- x-editable --> + <link type="text/css" href="components/x-editable/dist/bootstrap3-editable/css/bootstrap-editable.css" media="all" rel="stylesheet" /> + + <link rel="stylesheet" type="text/css" href="http://eclipse.org/orion/editor/releases/6.0/built-editor.css"/> + + <!-- Winery as last: Winrey also overwrites some definitions from above --> + <link rel="stylesheet" href="css/winery-common.css" /> + <link rel="stylesheet" href="css/topologytemplatecontent.css" /> + <link rel="stylesheet" href="css/topologymodeler.css" /> +</head> + +<body data-demo-id="drawingarea" data-library="jquery"> + +<div id="loading">loading...</div> + +<script type='text/javascript' src='${pageContext.request.contextPath}/components/requirejs/require.js'></script> +<script> + require.config({ + baseUrl: "${pageContext.request.contextPath}/js", + paths: { + "datatables": "../components/datatables/media/js/jquery.dataTables", + "jquery": "../components/jquery/jquery", + + "jquery.fileupload": "../components/blueimp-file-upload/js/jquery.fileupload", + "jquery.fileupload-ui": "../components/blueimp-file-upload/js/jquery.fileupload-ui", + "jquery.fileupload-process": "../components/blueimp-file-upload/js/jquery.fileupload-process", + "jquery.ui.widget": "../components/blueimp-file-upload/js/vendor/jquery.ui.widget", + + // required for jsplumb + "jquery.ui": "../3rdparty/jquery-ui/js/jquery-ui", + + "jsplumb": "../components/jsPlumb/dist/js/jquery.jsPlumb-1.5.4", + + "keyboardjs": "../components/KeyboardJS/keyboard", + "orioneditor": "http://eclipse.org/orion/editor/releases/6.0/built-editor-amd", + "pnotify": "../components/pnotify/jquery.pnotify", + "select2": "../components/select2/select2", + "tmpl": "../components/blueimp-tmpl/js/tmpl", + "XMLWriter": "../components/XMLWriter/XMLWriter" + } + }); +</script> + +<script type='text/javascript' src='components/jquery/jquery.js'></script> + +<script type='text/javascript' src='3rdparty/jquery-ui/js/jquery-ui.js'></script> + +<script type='text/javascript' src='components/bootstrap/dist/js/bootstrap.js'></script> + +<script type='text/javascript' src='components/bootstrap-spinedit/js/bootstrap-spinedit.js'></script> + +<!-- udpate of jquery widget by jQuery-File-Upload, which requires jQuery UI 1.9 at least --> +<!-- <script type="text/javascript" src="components/jQuery-File-Upload/js/vendor/jquery.ui.widget.js"></script> --> + +<script type='text/javascript' src='components/jsPlumb/dist/js/jquery.jsPlumb-1.5.4.js'></script> + +<script type="text/javascript" src="components/jquery-typing/plugin/jquery.typing-0.3.2.js"></script> + +<script type="text/javascript" src="components/select2/select2.js"></script> + +<script type="text/javascript" src="components/x-editable/dist/bootstrap3-editable/js/bootstrap-editable.js"></script> + +<script type="text/javascript" src="js/winery-common.js"></script> +<script type="text/javascript" src="js/winery-topologymodeler.js"></script> + +<jsp:include page="/jsp/shared/dialogs.jsp" /> + +<% + // only required for generating the CSS for each node type + Collection<TNodeType> allNodeTypes = client.getAllTypes(TNodeType.class); +%> + +<tmpl:CSSForTypes nodeTypes="<%=allNodeTypes%>" relationshipTypes="<%=relationshipTypes%>"/> + +<tmpl:propertiesBasic /> + +<t:about /> + +<script> +// global variable hodling data for relationship templates +if (!winery) winery = {}; + +// winery.connections is a hashmap from jsPlumb id to a data structure with +// .id = winery id; // The two ids DO NOT match. Explanation is at winery-common-topologyrendering.js +// ... +winery.connections = {}; + +// all x-editable popups should be placed in a way to fit "perfectly" on the screen +$.fn.editable.defaults.placement = "auto"; + +//configuration for pnotify +require(["jquery", "pnotify"], function() { + $.pnotify.defaults.styling = "bootstrap3"; +}); +</script> + +<%-- Begin: Add&Edit Req/Cap --%> + +<%List<QName> allTypes = client.getQNameListOfAllTypes(TRequirementType.class);%> +<tntrq:addorupdatereqorcap requirementOrCapability="requirement" shortName="Req" cssClassPrefix="requirements" headerLabel="Requirement" allTypes="<%=allTypes%>" clazz="<%=TRequirement.class%>" repositoryURL="<%=repositoryURL%>" /> + +<%allTypes = client.getQNameListOfAllTypes(TCapabilityType.class);%> +<tntrq:addorupdatereqorcap requirementOrCapability="capability" shortName="Cap" cssClassPrefix="capabilities" headerLabel="Capability" allTypes="<%=allTypes%>" clazz="<%=TCapability.class%>" repositoryURL="<%=repositoryURL%>" /> + +<%-- End: Add&Edit Req/Cap --%> + +<%allTypes = client.getQNameListOfAllTypes(TPolicyType.class);%> +<pol:policydiag allPolicyTypes="<%=allTypes%>" repositoryURL="<%=repositoryURL%>" /> + +<script> +"use strict"; + +// for debugging +//$.pnotify.defaults.remove = false; + +// global data structure +// .repositoryURL - the URL of the repository +// DOES NOT end with / +winery.repositoryURL = "<%=repositoryURL%>"; + +</script> + +<script> +/** + * Uses global variables currentlySelectedNodeTemplate and currentlySelectedDeploymentArtifactDiv + * + * @param atDelete (optional). Function to be called if the DA has been successfully deleted + */ +function askForDeploymentArtifactDeletion(atDelete) { + // we have to remove it on both the node template locally and on the server + // reason: if the user does NOT save after deletion, the repository would present him + // the DA again. Possibly with a dangeling reference to the artifact template, + // which might have been deleted if this DA was the last reference to it + + var daName = currentlySelectedDeploymentArtifactDiv.children("div.name").text(); + var url = "<%=topologyTemplateURL%>nodetemplates/" + encodeID(currentlySelectedNodeTemplate) + "/deploymentartifacts/" + daName; + deleteResource("this deployment artifact", url, function() { + currentlySelectedDeploymentArtifactDiv.remove(); + $("#DeploymentArtifactInfo").modal("hide"); + if (atDelete) { + atDelete(); + } + }); +} + +/** + * Queries the user for deletion of the currently selected DA. + * If the associated artifact template points to the current DA only, he is also ask if he wants to delete that template, too. + */ +function deleteDeploymentArtifact() { + var templateURL = $("#DAArtifactTemplate").attr("href"); + // an artifact template exists + // we have to ask the user if the wants to delete is as well + // IF this deployment artifact is the only reference to it + // The user has to be first asked for DA deletion and then for artifact template deletion + // Therefore, we pass a function to be executed AFTER deletion of the deployment artifact. + if (templateURL) { + // check for count + $.ajax({ + type: "GET", + async: false, + url: templateURL + "?referenceCount", + dataType: "text", + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not get count of artifact template usage", jqXHR, errorThrown); + }, + success: function(resData, textStatus, jqXHR) { + if (resData == "1") { + var atDelete = function() { + deleteResource("the associated artifact template", templateURL, function() {}); + }; + askForDeploymentArtifactDeletion(atDelete); + } else { + // more than one reference to the artifact template + // just ask for deletion of the DA and not of the AT + askForDeploymentArtifactDeletion(); + } + } + }); + } else { + askForDeploymentArtifactDeletion(); + } +} + +/** + * The user might have updated the XML information + * This has to be copied into the "storage" of the currently selected node template + * + * Uses global variable currentlySelectedDeploymentArtifactDiv + */ +function updateDeploymentArtifact() { + require(["winery-support-common"], function(wsc) { + var newVal = $("#DAXML").val(); + if (wsc.checkXMLValidityAndShowErrorIfInvalid(newVal)) { + currentlySelectedDeploymentArtifactDiv.children("textarea").val(newVal); + $("#DeploymentArtifactInfo").modal("hide"); + } + }); +} +</script> +<div class="modal fade" id="DeploymentArtifactInfo"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title">Artifact Information</h4> + </div> + <div class="modal-body"> + <form><fieldset> + <div class="form-group"> + <label for="DADname" class="control-label">Name</label> + <div id="DAname" class="form-control"></div> + </div> + + <div class="form-group"> + <label for="DAArtifactType" class="control-label">Artifact Type</label> + <a id="DAArtifactType" class="form-control" target="_blank"></a> + </div> + <div class="form-group"> + <label for="DAArtifactTemplate" class="control-label">Artifact Template</label> + <a id="DAArtifactTemplate" class="form-control" target="_blank"></a> + </div> + <div class="form-group"> + <label for=DAXML" class="control-label">XML</label> + <textarea id="DAXML" cols=50 rows=7 class="form-control"></textarea> + </div> + </fieldset></form> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> + <button type="button" class="btn btn-danger" onclick="deleteDeploymentArtifact();">Delete</button> + <button type="button" class="btn btn-primary" onclick="updateDeploymentArtifact();">Ok</button> + </div> + </div> + </div> +</div> + +<!-- BEGIN TOPOLOGY COMPLETION --> +<tc:selectionDialogs repositoryURL='<%=repositoryURL%>' serviceTemplateName='<%=serviceTemplateName%>' topologyTemplateURL='<%=topologyTemplateURL%>'/> +<!-- END TOPOLOGY COMPLETION --> + +<ct:artifactcreationdialog + URL="getURLForDeploymanetArtifactGeneration()" + repositoryURL="<%=repositoryURL%>" + name="Deployment" + onSuccessfulArtifactCreationFunction="artifactAddedSuccessfully" + allNamespaces="<%=client.getNamespaces()%>" + isDeploymentArtifact="true" + allArtifactTypes="<%=client.getQNameListOfAllTypes(TArtifactType.class)%>" + defaultNSForArtifactTemplate="<%=ns%>" + > +</ct:artifactcreationdialog> + +<script> +function getURLForDeploymanetArtifactGeneration() { + return "<%=topologyTemplateURL%>nodetemplates/" + encodeID(currentlySelectedNodeTemplate) + "/deploymentartifacts/" +} +</script> + +<% +// we want to display the name of the artifact template, not the id +Collection<QNameWithName> artifactTemplateList = client.getListOfAllInstances(ArtifactTemplateId.class); +%> + +<div id="winery"> + <ncnt:propertiesOfOneNodeTemplate repositoryURL="<%=repositoryURL%>"/> + <ncrt:propertiesOfOneRelationshipTemplate relationshipTypes="<%=relationshipTypes%>" repositoryURL="<%=repositoryURL%>"/> + + <div id="topbar"> + + <button class="btn btn-success topbutton" onclick="winery.events.fire(winery.events.name.command.SAVE);" id="saveBtn" data-loading-text="Saving...">Save</button> + <div class="btn-group"> + <button class="btn btn-default" onclick="doLayout();">Layout</button> + <button class="btn btn-default" onclick="horizontalAlignment();">Align-h (|)</button> + <button class="btn btn-default" onclick="verticalAlignment();">Align-v (-)</button> + </div> + + <tmpl:toggleButtons /> + + <%-- confusing, because DELETE at this place in the management part deletes the whole entity, not just the selected one + <button class="btn btn-danger selectionOnly" onclick="winery.events.fire(winery.events.name.command.DELETE_SELECTION);">Delete</button> + --%> + + <button data-toggle="button" class="btn btn-default" onclick="togglePrintView(!$(this).hasClass('active'));">Print View</button> + + <div class="btn-group"> + <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Other <span class="caret"></span></button> + + <ul class="dropdown-menu" role="menu"> + <li><a href="#" onclick="completeTopology();">Complete Topology</a></li> + <li><a id="exportCSARbtn" href="<%=topologyTemplateURL%>../?csar" target="_blank">Export CSAR</a></li> + <li><a href="#" onclick="showAbout();">about</a></li> + </ul> + </div> + + + <script> + $("#exportCSARbtn").tooltip({ + placement: 'bottom', + html: false, + title: "Hold CTRL key to export XML only." + }); + $("#exportCSARbtn").on("click", function(evt) { + var url = $(this).attr("href"); + if (evt.ctrlKey) { + url = url.replace(/csar$/, "definitions"); + } + window.open(url); + return false; + }); + </script> + + <script> + function togglePrintView(showPrintView) { + if (showPrintView) { + winery.events.fire(winery.events.name.command.UNSELECT_ALL_NODETEMPLATES); + if (hidePalette) hidePalette(); + // showTypes(true); + $("#drawingarea").addClass("printview"); + $("#drawingarea").removeClass("editview"); + + // move labels 10 px up + // we have to do it here as jsPlumb currently paints the label on the line instead of above of it + // See https://groups.google.com/d/msg/jsplumb/zdyAdWcRta0/K6F2MrHBH1AJ + $(".relationshipTypeLabel").each(function(i, e) { + var pos = $(e).offset(); + pos.top = pos.top - 10; + $(e).offset(pos); + }); + } else { + $("#drawingarea").removeClass("printview"); + $("#drawingarea").addClass("editview"); + + // move labels 10 px down + // we have to do it here as jsPlumb currently paints the label on the line instead of above of it + $(".relationshipTypeLabel").each(function(i, e) { + var pos = $(e).offset(); + pos.top = pos.top + 10; + $(e).offset(pos); + }); + } + } + </script> + </div> + + <tmpl:defineCreateConnectorEndpointsFunction relationshipTypes="<%=relationshipTypes%>"/> + <t:palette client="<%=client%>" relationshipTypes="<%=relationshipTypes%>" repositoryURL="<%=repositoryURL%>"/> + + <div id="selectionbox"> + </div> + + <div class="unselectable" id="editorArea"> + <div id="drawingarea" class="unselectable editview"> + +<% + List<TEntityTemplate> templateList = topologyTemplate.getNodeTemplateOrRelationshipTemplate(); + List<TRelationshipTemplate> relationshipTemplates = new LinkedList<TRelationshipTemplate>(); + for (TEntityTemplate template: templateList) { + if (template instanceof TRelationshipTemplate) { + relationshipTemplates.add((TRelationshipTemplate) template); + } else { + TNodeTemplate nodeTemplate = (TNodeTemplate) template; + + // Get saved position + // x and y are stored as attributes of other namespaces + String left = ModelUtilities.getLeft(nodeTemplate); + String top = ModelUtilities.getTop(nodeTemplate); + %> + <nt:nodeTemplateRenderer client="<%=client%>" relationshipTypes="<%=relationshipTypes%>" repositoryURL="<%=repositoryURL%>" nodeTemplate="<%=nodeTemplate%>" top="<%=top%>" left="<%=left%>"/> + <% + } + } + %> + </div> + </div> + + <script> + var multiDNDmode = false; + var multiDNDdata = {}; + + $(document).on("dragstart", "div.NodeTemplateShape", function(e) { + var nodeTemplateShape = $(this); + hideNodeTemplateShapeChangeBoxes(nodeTemplateShape); + if (nodeTemplateShape.hasClass("selected")) { + var allSelectedShapes = $("div.NodeTemplateShape.selected"); + if (allSelectedShapes.length > 1) { + // console.log("start: multiDNDmode"); + multiDNDdata.x = e.clientX; + multiDNDdata.y = e.clientY; + multiDNDdata.shapes = $("div.NodeTemplateShape.selected").not("#" + e.currentTarget.id); + multiDNDmode = true; + } + } else { + // "mousedown" event handling already took care about + // deselect everything else + // select shape + } + }); +</script> +<script> + $(document).on("drag", "div.NodeTemplateShape", function(e) { + if (multiDNDmode) { + // TODO possibly, this has to be put in a queue to avoid racing events? + var dx = e.clientX - multiDNDdata.x; + var dy = e.clientY - multiDNDdata.y; + multiDNDdata.x = e.clientX; + multiDNDdata.y = e.clientY; + multiDNDdata.shapes.each(function(i, n) { + n = $(n); + var offset = n.offset(); + offset.left += dx; + offset.top += dy; + n.offset(offset); + }); + jsPlumb.repaintEverything(); + } + }); + +</script> +<script> + + function doLayout() { + var editor = $("#editorArea"); + var nodeTemplates = editor.find(".NodeTemplateShape"); + require(["winery-sugiyamaLayouter"], function(layouter) { + layouter.layout(nodeTemplates); + }); + } + +</script> +<script> + + function horizontalAlignment() { + + var counter = 0; + var aggregatedLeft = 0; + + $("div.NodeTemplateShape.selected").each(function() { + aggregatedLeft = aggregatedLeft + $(this).position().left; + counter = counter + 1; + }); + + var newLeft = aggregatedLeft / counter; + + $("div.NodeTemplateShape.selected").each(function() { + jsPlumb.animate($(this).attr("id"), { + left : newLeft + }, { + duration : 500, + easing : 'easeOutBack' + }); + }); + + } + +</script> +<script> + + function verticalAlignment() { + + var counter = 0; + var aggregatedTop = 0; + + $("div.NodeTemplateShape.selected").each(function() { + aggregatedTop = aggregatedTop + $(this).position().top; + counter = counter + 1; + }); + + var newTop = aggregatedTop / counter; + + $("div.NodeTemplateShape.selected").each(function() { + jsPlumb.animate($(this).attr("id"), { + top : newTop + }, { + duration : 500, + easing : 'easeOutBack' + }); + }); + + } + +</script> +<script> +require(["winery-topologymodeler-AMD"], function(wt) { + winery.events.register(winery.events.name.command.SAVE, wt.save); + wt.setTopologyTemplateURL("<%=topologyTemplateURL%>"); +}); +</script> +<script> + + // "mousedown" instead of "click" enables a more Visio-like behavior + $(document).on("mousedown", "div.NodeTemplateShape", function(e) { + var target = $(e.target); + + // no special handling if x-editable popover is clicked + if (target.parents().hasClass("popover")) { + return false; + } + + // no special handling if connectors are clicked + if (target.hasClass("connectorEndpoint") || target.hasClass("connectorBox") || target.hasClass("connectorLabel")) { + return false; + } + + if (target.is("a") && !target.hasClass("editable")) { + // Link clicked + // Open in new tab + // Delay opening for 300ms to disalbe a dragstart + window.setTimeout(function() { + var href = target.attr("href"); + window.open(href); + }, 300); + return false; + } + + // if the deployment artifact "buttons" are clicked, handle that functionality + // class addOrLinkDA is also present + if (target.hasClass("addDA")) { + var nodeTemplate = $(this); + hideNodeTemplateShapeChangeBoxes(nodeTemplate); + var id = nodeTemplate.attr('id'); + + // adding DAs only works for existing resources + // Reason: the POST is directed to the "deploymentartifacts" sub resource, + // which does all the artifact template autocreation magic + $.ajax({ + type: "HEAD", + async: false, + url: "<%=topologyTemplateURL%>nodetemplates/" + encodeID(id) + "/", + error: function(jqXHR, textStatus, errorThrown) { + vShowError("Node template does not exist on the server. Please save the topology before adding deployment artifacts"); + }, + success: function(resData, textStatus, jqXHR) { + // setting this global variable is required for getURLForDeploymanetArtifactGeneration(), with which the dialog has been initalized. + currentlySelectedNodeTemplate = id; + openAddDeploymentArtifactDiag(); + } + }); + + return false; + } + + if (target.is("button")) { + // "Edit XML" or "Add deployment artifact" clicked + return false; + } + + // if the custom KV properties are clicked, handle them + if (target.hasClass("KVPropertyValue")) { + return false; + } + // OK or Cancel clicked at editable + if ((target.hasClass("icon-ok")) || (target.hasClass("icon-remove"))) { + return false; + } + + if (target.hasClass("reqorcap")) { + var reqOrCapId = undefined; // set to undefined to avoid compiler warnings + // check if req or cap should be edited + var parentReqOrCapDiv = undefined; // used to determine whether a req or cap is edited, set to undefined to avoid compiler warnings + if (target.is("div")) { + reqOrCapId = target.parent().attr("id"); + parentReqOrCapDiv = target.parent(); + } else { + vShowError("Wrong branch. UI is not consistent with code"); + } + var isReq = parentReqOrCapDiv.hasClass("requirements"); + if (isReq) { + showAddOrUpdateDiagForReq(undefined, reqOrCapId); + } else { + //console.log(parentReqOrCapDiv.hasClass("capabilities")); + showAddOrUpdateDiagForCap(undefined, reqOrCapId); + } + return false; + } + + if (target.hasClass("policy")) { + // click is always on the seen policy content (name, template, ...) + // the complete element is the parent element + var policy = $(target).parent(); + showUpdateDiagForPolicy(policy); + return false; + } + + if ( (e.shiftKey) || (e.ctrlKey) ) { + // SHIFT or CTRL indicates multi select + // toggle containment in the multi select + $(this).toggleClass("selected"); + } else { + // no explicit multi select + var numSelected = $("div.NodeTemplateShape.selected").length; + if ($(this).hasClass("selected")) { + // selection if already exists + + // Below, we raise the selection change even in that case to provoke an update of properties etc. + // When dragging and dropping a single node, the menu of the node is not shown any more + // a click on the (still selected) node should reveal the menu entries. + } else { + // curent shape not selected + + if (numSelected > 0) { + // other shapes are selected + // the clicked shape is clicked + // that means, all other shapes should be unselected + $("div.NodeTemplateShape.selected").removeClass("selected"); + } + + // no multi select trigger + // shape is unselected + // finally, select the shape + $(this).addClass("selected"); + } + } + + winery.events.fire(winery.events.name.SELECTION_CHANGED); + return false; + }); + +</script> +<script> + + // we cannot use "$("#editorArea").on("click") as this is *always* triggered before $(document).on("click", ...) + $(document).on("mousedown", "#editorArea", function(e) { + hidePalette(); + + winery.events.fire(winery.events.name.command.UNSELECT_ALL_NODETEMPLATES); + + hideRTViewOnTheRight(); + unselectAllConnections(); + + // true because jsPlumb COULD treat this event, currently unclear + return true; + }); + + /** marquee tool **/ + + var selectionBoxMode = false; + var selectionBox = {}; + + /** + * This function is called when selectionBoxMode = true and the mouse gets moved + */ + var selectionBoxModeMouseMoveFunction = function(e) { + selectionBox.endX = e.pageX; + selectionBox.endY = e.pageY; + + // fix selectionbox coordinates if they are out of the window + if (selectionBox.endX < selectionBox.minx) selectionBox.endX = selectionBox.minx; + if (selectionBox.endX > selectionBox.maxx) selectionBox.endX = selectionBox.maxx; + if (selectionBox.endY < selectionBox.miny) selectionBox.endY = selectionBox.miny; + if (selectionBox.endY > selectionBox.maxy) selectionBox.endY = selectionBox.maxy; + + // we cannot show the selection box at mousedown as this conflicts somehow with jsPlumb + // if the .offset of the selectionbox is set, jsPlumb events are not fired any more + $("#selectionbox").show(); + // setSelectionBoxCoordinates() only works if selectionbox is shown + setSelectionBoxCoordinates(); + } + + function setSelectionBoxCoordinates() { + var x; + var y; + var height; + var width; + + // adjust parameters for html, where top/left have to be smaller than lower right + if (selectionBox.startX < selectionBox.endX) { + x = selectionBox.startX; + width = selectionBox.endX - selectionBox.startX; + } else { + x = selectionBox.endX; + width = selectionBox.startX - selectionBox.endX; + } + if (selectionBox.startY < selectionBox.endY) { + y = selectionBox.startY; + height = selectionBox.endY - selectionBox.startY; + } else { + y = selectionBox.endY; + height = selectionBox.startY - selectionBox.endY; + } + + $("#selectionbox").offset({ + left : x, + top : y + }); + $("#selectionbox").width(width); + $("#selectionbox").height(height); + + // console.log("realx: " + $("#selectionbox").offset().left); + // console.log("realy: " + $("#selectionbox").offset().top); + + selectionBox.x = x; + selectionBox.y = y; + selectionBox.height = height; + selectionBox.width = width; + + var area = {}; + if (lastSelectionBox == undefined) { + // nothing selected at last, check whole new box + area = selectionBox; + } else { + // TODO + // calculate area to check + } + + // console.log("sel: " + x + "/" + y + " --X dim: " + width + "/" + height); + + // quick hack: we just go through all node templates and check them for selection + $("div.NodeTemplateShape:not('.hidden')").each(function(index, nodeTemplate) { + nodeTemplate = $(nodeTemplate); + var nx = nodeTemplate.offset().left; + var ny = nodeTemplate.offset().top; + var nw = nodeTemplate.width(); + var nh = nodeTemplate.height(); + /* console.log(nx + "/" + ny + " --> dim: " + nw + "/" + nh); */ + if (nx >= x && + ny >= y && + nx + nw <= x + width && + ny + nh <= y + height) { + nodeTemplate.addClass("selected"); + } else { + nodeTemplate.removeClass("selected"); + } + }); + + lastSelectionBox = selectionBox; + } + + // register selection box handling events + $(document).on("mousedown", "#editorArea", function(e) { + selectionBoxMode = true; + selectionBox.startX = e.pageX; + selectionBox.startY = e.pageY; + selectionBox.endX = selectionBox.startX; + selectionBox.endY = selectionBox.startY; + // console.log("Start: " + selectionBox.startX + "/" + selectionBox.startY) + selectionBox.minx = document.body.scrollLeft; + selectionBox.miny = document.body.scrollTop; + selectionBox.maxx = selectionBox.minx + $(window).width(); + selectionBox.maxy = selectionBox.miny + $(window).height(); + lastSelectionBox = undefined; + $(document).on("mousemove", selectionBoxModeMouseMoveFunction); + return true; + }); + $(document).on("mouseup", function(e) { + // TODO: possibly, dragend could be used. With the recent libraries, it also works in Chrome + if (selectionBoxMode) { + $(document).off("mousemove", selectionBoxModeMouseMoveFunction); + selectionBoxMode = false; + $("#selectionbox").hide(); + } else if (multiDNDmode) { + multiDNDmode = false; + // console.log("end: multiDNDmode"); + } + }); + +</script> +<script> + + /** + * register events / event registering / eventing + */ + $(function() { + winery.events.register( + winery.events.name.SELECTION_CHANGED, + function() { + var numSelected = $("div.NodeTemplateShape.selected").length; + + if (numSelected == 1) { + + var selectedNodeTemplate = $("div.NodeTemplateShape.selected"); + if (isShownNodeTemplateShapeChangeBoxes(selectedNodeTemplate)) { + // shape change boxes are already shown. Hide them + hideNodeTemplateShapeChangeBoxes($("div.NodeTemplateShape")); + } else { + // fired if + // * a single node template is selected, + // * no menu is shown + + // bring that shape to the front + $("div.NodeTemplateShape").css("z-index", "20"); + selectedNodeTemplate.css("z-index", "21"); + + // we show the change boxes + showNodeTemplateShapeChangeBoxes(selectedNodeTemplate); + hideNodeTemplateShapeChangeBoxes($("div.NodeTemplateShape:not(.selected)")); + } + } else { + // hide everywhere + hideNodeTemplateShapeChangeBoxes($("div.NodeTemplateShape")); + } + + updateVisibilityToggleButtons(); + } + ); + winery.events.register( + winery.events.name.command.SELECT_ALL_NODETEMPLATES, + function () { + $("div.NodeTemplateShape").addClass("selected"); + winery.events.fire(winery.events.name.SELECTION_CHANGED); + } + ); + winery.events.register( + winery.events.name.command.UNSELECT_ALL_NODETEMPLATES, + function () { + $("div.NodeTemplateShape").removeClass("selected"); + winery.events.fire(winery.events.name.SELECTION_CHANGED); + } + ); + + winery.events.register( + winery.events.name.command.MOVE_UP, + function () { + wineryMoveSelectedNodeTemplateShapes(0, -10); + return false; + } + ); + winery.events.register( + winery.events.name.command.MOVE_DOWN, + function () { + wineryMoveSelectedNodeTemplateShapes(0, 10); + return false; + } + ); + winery.events.register( + winery.events.name.command.MOVE_LEFT, + function () { + wineryMoveSelectedNodeTemplateShapes(-10, 0); + return false; + } + ); + winery.events.register( + winery.events.name.command.MOVE_RIGHT, + function () { + wineryMoveSelectedNodeTemplateShapes(10, 0); + return false; + } + ); + + winery.events.register(winery.events.name.command.DELETE_SELECTION, function() { + if ($(":focus").length == 0) { + // only delete something if no input field is focused + // otherwise, a deletion of a character leads to a deletion of the selected node + var nodesToDelete = $("div.NodeTemplateShape.selected"); + if (nodesToDelete.size() > 0) { + nodesToDelete.each(function(idx, n) { + var outEdges = jsPlumb.select({source:n.id}); + outEdges.detach(); + var inEdges = jsPlumb.select({target:n.id}); + inEdges.detach(); + }); + nodesToDelete.remove(); + } else { + jsPlumb.select().each(function(connection) { + if (connection.hasType("selected")) { + jsPlumb.detach(connection); + // handleConnectionRemoved is fired by detach, + // this handles the proper data model updates + } + }); + } + }; + }); + }); + +</script> +<script> + + /** + * Initialization code + */ + $(function() { + // We need this variable to avoid adding drop targets multiple times + // The dragenter event is triggered if the current trag leaves a sub element, too + var dragEnterCount = 0; + var firstDrop = true; + + // hack for firefox 19.0.2 publishing the dragenter event twice in some cases (when firebug is not active) + var lastElement = ""; + + var divDemo = $("html"); + divDemo.on("dragenter", function(event) { + if (firstDrop) { + $(".addnewartifacttemplate").show(); + firstDrop = false; + } + if (lastElement != event.target) { + dragEnterCount++; + lastElement = event.target; + } + }); + divDemo.on("dragleave", function(event) { + dragEnterCount--; + lastElement = ""; + if (dragEnterCount==0) { + $(".addnewartifacttemplate").hide(); + lastElement = ""; + firstDrop = true; + } + }); + + // disable dragover to enable drag'n'drop of files + // see https://github.com/blueimp/jQuery-File-Upload/wiki/Multiple-File-Upload-Widgets-on-the-same-page + $(document).on("drop dragover", function(e) { + e.preventDefault(); + e.stopPropagation(); + }); + + $(document).on("drop", function(e) { + if (e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files) { + e.preventDefault(); + e.stopPropagation(); + } + }); + + // A call is not necessary - the button should have been intialized correctly at the loading by having the right class (active) where appropriate + // updateVisibilityToggleButtons(); + }); +</script> +<script> + // fire KeyboardJS initialization in parallel to loading + require(["keyboardjs"], function(KeyboardJS) { + KeyboardJS.on('del', function(event, keys, keyComboStr) { + if (!keyComboAllowed()) { + return true; + } else { + winery.events.fire(winery.events.name.command.DELETE_SELECTION); + } + }); + + KeyboardJS.on('ctrl + s', function(event, keys, keyComboStr) { + winery.events.fire(winery.events.name.command.SAVE); + + // disable triggering saving by browser + return false; + }); + + KeyboardJS.on('ctrl + a', function(event, keys, keyComboStr) { + if (!keyComboAllowed()) { + // CTRL+a on an input element should trigger selecting all text + return true; + } else { + // otherwise, we select all node templates + winery.events.fire(winery.events.name.command.SELECT_ALL_NODETEMPLATES); + return false; + } + }); + + KeyboardJS.on('up', function(event, keys, keyComboStr) { + if (!keyComboAllowedAndNodeTemplatesSelected()) { + return true; + } else { + winery.events.fire(winery.events.name.command.MOVE_UP); + return false; + } + }); + KeyboardJS.on('down', function(event, keys, keyComboStr) { + if (!keyComboAllowedAndNodeTemplatesSelected()) { + // down on an input, when a dialog is shown or when no nodeTemplate is selected, should trigger the default action + return true; + } else { + // otherwise, we move the selected node templates down + winery.events.fire(winery.events.name.command.MOVE_DOWN); + return false; + } + }); + KeyboardJS.on('left', function(event, keys, keyComboStr) { + if (!keyComboAllowedAndNodeTemplatesSelected()) { + return true; + } else { + winery.events.fire(winery.events.name.command.MOVE_LEFT); + return false; + } + }); + KeyboardJS.on('right', function(event, keys, keyComboStr) { + if (!keyComboAllowedAndNodeTemplatesSelected()) { + return true; + } else { + winery.events.fire(winery.events.name.command.MOVE_RIGHT); + return false; + } + }); + }); +</script> +<script> + + function updateVisibilityToggleButtons() { + + if ($("div.NodeTemplateShape.selected").size() == 0) { + + // show buttons active if all parts are visible + // show buttons inactive if any part is not visible + if ($("div.NodeTemplateShape:visible .deploymentArtifactsContainer:visible").size() == $("div.NodeTemplateShape:visible .deploymentArtifactsContainer").size()) { + $('#toggleDeploymentArtifactsVisibility').addClass('active'); + } else { + $('#toggleDeploymentArtifactsVisibility').removeClass('active'); + } + if ($("div.NodeTemplateShape:visible .propertiesContainer:visible").size() == $("div.NodeTemplateShape:visible .propertiesContainer").size()) { + $('#togglePropertiesVisibility').addClass('active'); + } else { + $('#togglePropertiesVisibility').removeClass('active'); + } + if ($("div.NodeTemplateShape:visible div.type.nodetemplate:visible").size() == $("div.NodeTemplateShape:visible div.type.nodetemplate").size()) { + $('#toggleTypeVisibility').addClass('active'); + } else { + $('#toggleTypeVisibility').removeClass('active'); + } + if ($("div.NodeTemplateShape:visible div.id.nodetemplate:visible").size() == $("div.NodeTemplateShape:visible div.id.nodetemplate").size()) { + $('#toggleIdVisibility').addClass('active'); + } else { + $('#toggleIdVisibility').removeClass('active'); + } + + } else { + if ($("div.NodeTemplateShape.selected .deploymentArtifactsContainer:visible").size() > 0) { + $('#toggleDeploymentArtifactsVisibility').addClass('active'); + } else { + $('#toggleDeploymentArtifactsVisibility').removeClass('active'); + } + if ($("div.NodeTemplateShape.selected .propertiesContainer:visible").size() > 0) { + $('#togglePropertiesVisibility').addClass('active'); + } else { + $('#togglePropertiesVisibility').removeClass('active'); + } + if ($("div.NodeTemplateShape.selected div.type.nodetemplate:visible").size() > 0) { + $('#toggleTypeVisibility').addClass('active'); + } else { + $('#toggleTypeVisibility').removeClass('active'); + } + if ($("div.toggleIdVisibility.selected div.id.nodetemplate:visible").size() > 0) { + $('#toggleTypeVisibility').addClass('active'); + } else { + $('#toggleIdVisibility').removeClass('active'); + } + } + } + +</script> + +<script> + + function handleConnectionRemoved(data) { + var id = data.connection.id; + + // QUICK HACK: trigger rerouting of arrows + // jsPlumb should do it automatically, but in the winery setup, it does not + window.setTimeout(function() { + jsPlumb.repaint($("#" + data.targetId)); + }, 300); + + + delete winery.connections[id]; + } + + jsPlumb.bind("ready", function() { + jsPlumb.importDefaults({ + DragOptions : { cursor: "pointer", zIndex:2000 }, + HoverClass:"connector-hover" + }); + + jsPlumb.bind("connectionDrag", function(conn) { + isInConnectionMode = true; + winery.events.fire(winery.events.name.command.UNSELECT_ALL_NODETEMPLATES); + return true; + }); + + jsPlumb.bind("beforeDrop", function(sourceId, targetId, scope, connection, dropEndpoint) { + isInConnectionMode = false; + return true; + }); + + jsPlumb.bind("connectionDetached", function(connection) { + handleConnectionRemoved(connection); + }); + + jsPlumb.bind("click", function(conn, originalEvent) { + if (!conn.hasType("selected")) { + unselectAllConnections(); + conn.addType("selected"); + showRTViewOnTheRight(conn); + } else { + conn.removeType("selected"); + // we have to go through all connections by ourselves to find out the number of selected ones + var selectedConn = undefined; + var count = 0; + jsPlumb.select().each(function(connection) { + if (connection.hasType("selected")) { + count++; + selectedConn = connection; + } + }); + if (count == 1) { + showRTViewOnTheRight(selectedConn); + } else { + hideRTViewOnTheRight(); + } + } + }); + // jsPlumb.ready + }); + </script> + + +<%-- ===== BEGIN: enable editing properties of requirements and capabilities ===== --%> +<div class="hidden" id="skelettonContainerForRequirements"> + <%-- create property value holders for each requirement type; used for NEWLY created requirements --%> + <c:forEach items="<%=client.getAllTypes(TRequirementType.class)%>" var="type"> + <div class="skelettonPropertyEditorForReq"> + <span class="typeQName">{${type.targetNamespace}}${type.name}</span> + <tmpl:properties + propertiesDefinition="${type.propertiesDefinition}" + wpd="${wc:winerysPropertiesDefinition(type)}" + template="<%=null%>" + pathToImages="images/"> + </tmpl:properties> + </div> + </c:forEach> +</div> +<div class="hidden" id="skelettonContainerForCapabilities"> + <%-- create property value holders for each capability type; used for NEWLY created capability --%> + <c:forEach items="<%=client.getAllTypes(TCapabilityType.class)%>" var="type"> + <div class="skelettonPropertyEditorForCap"> + <span class="typeQName">{${type.targetNamespace}}${type.name}</span> + <tmpl:properties + propertiesDefinition="${type.propertiesDefinition}" + wpd="${wc:winerysPropertiesDefinition(type)}" + template="<%=null%>" + pathToImages="images/"> + </tmpl:properties> + </div> + </c:forEach> +</div> + +<script> +// Initialize skeletton editor does NOT work with the current clone thing, we have to initialize the editor after the clone add addreqorcap.tag +</script> +<%-- ===== END: enable editing properties of requirements and capabilities ===== --%> + + +<%-- ===== BEGIN: enable editing properties of relationship types ===== --%> +<%-- idea: + * create editor in skelettonContainerForRelationshipTemplates, + * move it to the properties for editing, + * and move it back to skelettonContainerForRelationshipTemplates after editing +--%> + +<div class="hidden" id="skelettonContainerForRelationshipTemplates"> + <%-- create property value holders for each relationship type; used for NEWLY created relationship templates --%> + <c:forEach items="<%=relationshipTypes%>" var="relationshipType"> + <div class="skelettonPropertyEditorForRelationshipTemplate"> + <span class="typeQName">{${relationshipType.targetNamespace}}${relationshipType.name}</span> + <tmpl:properties + propertiesDefinition="${relationshipType.propertiesDefinition}" + wpd="${wc:winerysPropertiesDefinition(relationshipType)}" + template="<%=null%>" + pathToImages="images/"> + </tmpl:properties> + </div> + </c:forEach> + + <%-- create property value holders for each existing relationship template; used for existing relationship templates--%> + <c:forEach items="<%=relationshipTemplates%>" var="relationshipTemplate"> + <%-- data-id stores the id of the relationship template --%> + <div class="propertyEditorForRelationshipTemplate" data-id="${relationshipTemplate.id}"> + <c:set var="typeQName" value="${relationshipTemplate.type}" /> + <c:set var="relationshipType" value="<%=client.getType((javax.xml.namespace.QName) pageContext.getAttribute(\"typeQName\"), TRelationshipType.class)%>" /> + <tmpl:properties + propertiesDefinition="${relationshipType.propertiesDefinition}" + wpd="${wc:winerysPropertiesDefinition(relationshipType)}" + template="${relationshipTemplate}" + pathToImages="images/"> + </tmpl:properties> + </div> + </c:forEach> +</div> + +<script> +// Initialize skeletton editor +// A clone also clones the editing functionality +$(".skelettonPropertyEditorForRelationshipTemplate").find(".KVPropertyValue").editable(); +</script> + +<script> +var tmpRelationshipTemplateProperties = {}; + +function onDoneRegisterConnectionTypesAndConnectNodeTemplates() { + // as soon as all connection types are registered, + // winery.connections[] is filled + + // winery.connections is a hashmap from jsPlumb ids to a datastructure containing the winery id + // the jsPlumb id is NOT equal to the winery id + // therefore, we have to create a map between winery id and jsplumb id + var mapTOSCAIdToJSPlumbId = {}; + $.each(winery.connections, function(i, e) { + mapTOSCAIdToJSPlumbId[e.id] = i; + }); + + // we have to move PropertyEditorForRelationshipTemplate->*ID* to winery.connections[*ID*].propertiesHTML + // we also init x-editable + $(".propertyEditorForRelationshipTemplate").each(function(i,e) { + var id = $(e).data("id"); + var propertiesContainer = $(e).children("div.propertiesContainer"); + if (propertiesContainer.length != 0) { + // properties exist + // move the properties container + id = mapTOSCAIdToJSPlumbId[id]; + winery.connections[id].propertiesContainer = propertiesContainer; + propertiesContainer.find(".KVPropertyValue").editable(); + } + }); + + // we have to bind here and not in propertiesOfOneRelationshipTemplate.tag as we only want to listen to *usercreated* connections, + // not to existing ones + jsPlumb.bind("connection", function(data) { + // similar to winery-common-topologyrendering -> handleConnectionCreated + var conn; + if (data.connection) { + conn = data.connection; + } else { + conn = data; + } + + var id = conn.id; + var type = conn.getType()[0]; + var propertiesContainer= $(".skelettonPropertyEditorForRelationshipTemplate > span:contains('" + type + "')").parent().children("div"); + winery.connections[id].propertiesContainer = propertiesContainer; + }); + + // finally, we have finished loading + $('#loading').fadeOut(); +} +</script> +<%-- ===== END: enable editing properties of relationship types ===== --%> + +<tmpl:registerConnectionTypesAndConnectNodeTemplates relationshipTemplates="<%=relationshipTemplates%>" relationshipTypes="<%=relationshipTypes %>" ondone="onDoneRegisterConnectionTypesAndConnectNodeTemplates();" repositoryURL="<%=repositoryURL%>"/> + +<!-- The template to display files available for upload --> +<script id="template-upload" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-upload fade"> + <td> + <span class="preview"></span> + </td> + <td> + <p class="name">{%=file.name%}</p> + {% if (file.error) { %} + <div><span class="label label-important">Error</span> {%=file.error%}</div> + {% } %} + </td> + <td> + <p class="size">{%=o.formatFileSize(file.size)%}</p> + {% if (!o.files.error) { %} + <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="bar" style="width:0%;"></div></div> + {% } %} + </td> + <td> + {% if (!o.files.error && !i && !o.options.autoUpload) { %} + <button class="btn btn-primary start"> + <i class="icon-upload icon-white"></i> + <span>Start</span> + </button> + {% } %} + {% if (!i) { %} + <button class="btn btn-warning cancel"> + <i class="icon-ban-circle icon-white"></i> + <span>Cancel</span> + </button> + {% } %} + </td> + </tr> +{% } %} +</script> +<!-- The template to display files available for download --> +<script id="template-download" type="text/x-tmpl"> +{% for (var i=0, file; file=o.files[i]; i++) { %} + <tr class="template-download fade"> + <td> + <span class="preview"> + {% if (file.thumbnailUrl) { %} + <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" data-gallery><img src="{%=file.thumbnailUrl%}"></a> + {% } %} + </span> + </td> + <td> + <p class="name"> + <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a> + </p> + {% if (file.error) { %} + <div><span class="label label-important">Error</span> {%=file.error%}</div> + {% } %} + </td> + <td> + <span class="size">{%=o.formatFileSize(file.size)%}</span> + </td> + <td> + <button class="btn btn-danger delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}"{% if (file.deleteWithCredentials) { %} data-xhr-fields='{"withCredentials":true}'{% } %}> + <i class="icon-trash icon-white"></i> + <span>Delete</span> + </button> + </td> + </tr> +{% } %} +</script> + +</div> + +<script type="text/x-tmpl" id="tmpl-deploymentArtifact"> + <div class="deploymentArtifact row" onclick="showDeploymentArtifactInformation('{%=o.nodeTemplateId%}', '{%=o.name%}');"> + <textarea class="hidden">{%=o.xml%}</textarea> + <div class="col-xs-4 overflowhidden deploymentArtifact name">{%=o.name%}</div> + <div class="col-xs-4 overflowhidden artifactTemplate">{% if (o.artifactTemplateName) { %}{%=o.artifactTemplateName%}{% } %}</div> + <div class="col-xs-4 overflowhidden artifactType">{%=o.artifactTypeName%}</div> + </div> +</script> + +<script type="text/x-tmpl" id="tmpl-deploymentArtifactXML"> + <tosca:DeploymentArtifact + name="{%=o.name%}" + xmlns:ns1="{%=o.artifactTypeNSAndId.namespace%}" + artifactType="ns1:{%=o.artifactTypeNSAndId.localname%}" + {% if (o.artifactTemplateNSAndId) { %} + xmlns:ns2="{%=o.artifactTemplateNSAndId.namespace%}" + artifactRef="ns2:{%=o.artifactTemplateNSAndId.localname%}" + {% } %} + xmlns:tosca="<%=org.eclipse.winery.common.constants.Namespaces.TOSCA_NAMESPACE%>" /> +</script> + +<%-- param: value, selected (optional), text --%> +<script type="text/x-tmpl" id="tmpl-option"> +<option value="{%=o.value%}"{% if (o.selected) { %} selected="selected"{% } %}>{%=o.text%}</option> +</script> + + </body> +</html> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/artifacttemplateselection.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/artifacttemplateselection.js new file mode 100644 index 0000000..7b7f20b --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/artifacttemplateselection.js @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ + + // also loaded from the repository + +// TODO: winery-common should be required -> encodeID; typing could be required (but that is no AMD module) +define([], function() { + $("#artifactTemplateName").typing({ + start: function(event, $elem) { + flagArtifactTemplateNameAsUpdating(); + }, + stop: function(event, $elem) { + checkArtifactTemplateName(); + } + }); + + $("#artifactTemplateNS").on("blur", checkArtifactTemplateName).on("change", checkArtifactTemplateName).on("focus", flagArtifactTemplateNameAsUpdating); + + var repositoryURL; + + return { + setRepositoryURL: function(url) { + repositoryURL = url; + }, + checkArtifactTemplateName: checkArtifactTemplateName, + flagArtifactTemplateNameAsUpdating: flagArtifactTemplateNameAsUpdating + }; + + function checkArtifactTemplateName() { + var ns = $("#artifactTemplateNS").val(); + var name = $("#artifactTemplateName").val(); + var url = repositoryURL + "/artifacttemplates/" + encodeID(ns) + "/" + encodeID(name) + "/"; + if (name == "") { + var valid = false; + var invalidReason = "No name provided"; + setValidityStatus(valid, invalidReason); + } else { + $.ajax(url, { + type: 'HEAD', + dataType: 'html', + error: function(jqXHR, textStatus, errorThrown) { + if (jqXHR.status == 404) { + // artifact template does not exist: everything is allright + setValidityStatus(true, null); + } else { + setValidityStatus(false, textStatus); + } + }, + success: function(data, textStatus, jqXHR) { + setValidityStatus(false, "artifact template already exists"); + } + }); + } + } + + function flagArtifactTemplateNameAsUpdating() { + $("#artifactTemplateNameIsValid").removeClass("invalid").removeClass("valid").addClass("unknown"); + $("#artifactTemplateNameIsInvalidReason").text(""); + } + + function setValidityStatus(valid, invalidReason) { + $("#artifactTemplateNameIsValid").removeClass("unknown"); + if (valid) { + $("#artifactTemplateNameIsValid").addClass("valid"); + $("#artifactTemplateNameIsInvalidReason").text("Ok"); + } else { + $("#artifactTemplateNameIsValid").addClass("invalid"); + $("#artifactTemplateNameIsInvalidReason").text(invalidReason); + } + } + + +});
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-audio.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-audio.js new file mode 100644 index 0000000..20e2cde --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-audio.js @@ -0,0 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ + +// dummy as we don't need the functionality, but jquery.fileupload-ui.js requires it
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-image.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-image.js new file mode 100644 index 0000000..20e2cde --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-image.js @@ -0,0 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ + +// dummy as we don't need the functionality, but jquery.fileupload-ui.js requires it
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-validate.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-validate.js new file mode 100644 index 0000000..20e2cde --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-validate.js @@ -0,0 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ + +// dummy as we don't need the functionality, but jquery.fileupload-ui.js requires it
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-video.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-video.js new file mode 100644 index 0000000..20e2cde --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/jquery.fileupload-video.js @@ -0,0 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ + +// dummy as we don't need the functionality, but jquery.fileupload-ui.js requires it
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common-topologyrendering.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common-topologyrendering.js new file mode 100644 index 0000000..fa37897 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common-topologyrendering.js @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ +/** + * This file contains supporting functions for the rendering a topology template + */ +define( + ["jsplumb", "winery-support-common"], + function (globdefa, wsc) { + var readOnly = false; + + var module = { + initNodeTemplate: initNodeTemplate, + handleConnectionCreated: handleConnectionCreated, + imageError: imageError, + setReadOnly: setReadOnly + }; + + return module; + + /** + * @param nodeTemplateShape the set of node template shapes to initialize + * @param makeDraggable true if the nodeTemplates should be made draggable + */ + function initNodeTemplate(nodeTemplateShapeSet, makeDraggable) { + if (makeDraggable) { + jsPlumb.draggable(nodeTemplateShapeSet); + } + jsPlumb.makeTarget(nodeTemplateShapeSet, { + anchor:"Continuous", + endpoint:"Blank" + }); + + // this function is defined in index.jsp via jsp functions + // as it depends on the available relationship types + createConnectorEndpoints(nodeTemplateShapeSet); + + nodeTemplateShapeSet.addClass("layoutableComponent"); + + nodeTemplateShapeSet.each(function(idx, s) { + var shape = $(s); + + var id = shape.attr("id"); + + // KV Properties + var props = shape.children(".propertiesContainer") + .children(".content") + .children("table") + .children("tbody"); + if (!readOnly) { + props.find(".KVPropertyValue").editable(); + } + + // Deployment Artifacts + var fu = shape.children(".deploymentArtifactsContainer") + .children(".content") + .children(".addnewartifacttemplate") + .children(".fileupload"); + fu.attr("data-url", "nodetemplates/" + wsc.encodeId(id) + "/deploymentartifacts/"); + }); + + // nodeTemplateShapeSet.children(".deploymentArtifactsContainer").children(".content").children(".deploymentArtifact").each(function(index, e) { + // addnewfileoverlay could be added here + // $(this). + // }); + } + + /** + * Handles the creation of connections by jsPlumb + * + * Also called if connection is created during loading + */ + function handleConnectionCreated(data) { + // might be called directly from here or by the event + // if called by jsPlumb infrastructure, we have to get rid of the surrounding element + var conn; + if (data.connection) { + conn = data.connection; + } else { + conn = data; + } + winery.debugConnData = conn; + + var id = conn.id; + winery.connections[id] = { + // we store the id to have a default for the id + id: id, + // and use it also as starting point of a name + name: id, + // we do NOT copy the plain type here + // type is stored in the connection + // type: .getType()[0] + // BUT: we copy the detailed ns and id + nsAndLocalName: wsc.getNamespaceAndLocalNameFromQName(conn.getType()[0]) + }; + putToolTipInfo(conn); + + // we have to manually show and hide the tooltips as Bootstrap's tooltip plugin does not work after a connection was highlighted. + conn.bind("mouseenter", function(conn,e) { + putToolTipInfo(conn); + }); + conn.bind("mouseexit", function(conn,e) { + $("div.tooltip").remove(); + // we have to replace the tooltip as + putToolTipInfo(conn); + }); + } + + function putToolTipInfo(conn) { + // add tooltip showing the relationship type + var svgElement = $(conn.canvas); + // the title attribute is shown in the tooltip + // set the relationship type as tooltip + // we show the localname only + var nsAndLocalName = winery.connections[conn.id].nsAndLocalName; + // Vino4TOSCA: type in brackets + var title = "(" + nsAndLocalName.localname + ")"; + svgElement.tooltip({title: title}); + } + + /** + * Removes the image from the display. Used at images which could not be loaded + * + * Used via {@code <img onerror="imageError(this);" ... />} + */ + function imageError(image) { + image.onError=""; + image.style.visibility = "hidden"; + } + + function setReadOnly() { + readOnly = true; + } + } +);
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common.js new file mode 100644 index 0000000..434bec3 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-common.js @@ -0,0 +1,265 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ + +/** +This .js file is shared between the Winery Repository and the Winery Topology Modeler + +This should be rewritten as AMD module. A start is made in winery-support-common.js. + +vConfirmYesNo is defined in jsp/shared/dialogs.jsp +*/ + +/** + * Highlights fields, which are required but not filled out by the user. + * The check for required fields is done via the CSS class "required" + * + * @return true if a required field is not filled with a value + */ +function highlightRequiredFields() { + var requiredFieldMissing = false; + // includes check input field attribute "required" of HTML5: http://www.w3.org/TR/html-markup/input.radio.html#input.radio.attrs.required + $("input.required:visible:enabled, input[required]:visible").each(function(){ + if ($(this).val() == '') { + $(this).parent().addClass('has-warning'); + requiredFieldMissing = true; + } else { + $(this).parent().removeClass('has-warning'); + } + }); + return requiredFieldMissing; +} + +function vPnotify(text, title, type) { + require(["pnotify"], function() { + var notice = $.pnotify({ + title: title, + text: text, + type: type + }); + notice.on("click", function(e) { + var target = $(e.target); + if (target.is(".ui-pnotify-closer *, .ui-pnotify-sticker *, a")) { + // buttons clicked - call their functionality + return true; + } else { + // click on text leads to display of a dialog showing the complete content + + var textDiv; + if (target.is("div.ui-pnotify-text")) { + textDiv = target; + } else { + textDiv = target.closest("div.ui-pnotify-container").find("div.ui-pnotify-text"); + } + + // put text into dialog and show it + $("#diagmessagetitle").text("Full notification"); + $("#diagmessagemsg").html(textDiv.html()); + $("#diagmessage").modal("show"); + + return false; + } + }); + }); +} + +/** + * @param title optional title + */ +function vShowError(text, title) { + vPnotify(text, title, "error"); +} + +function vShowAJAXError(msg, jqXHR, errorThrown) { + vShowError(msg + "<br />" + errorThrown + "<br/>" + jqXHR.responseText); +} + +/** + * @param title optional title + */ +function vShowNotification(text, title) { + vPnotify(text, title, "notification"); +} + + +/** + * @param title optional title + */ +function vShowSuccess(text, title) { + vPnotify(text, title, "success"); +} + +/** + * Deletes the given resource with confirmation. + * if deletion fails, an error message is shown + * + * @param onSuccess: function(data, textStatus, jqXHR) to call if deletion has been successful + * @param onError: function(jqXHR, textStatus, errorThrown) called if deletion lead to an error (optional) + * @param onStart: function() called if user has agreed to delete resource (optional) + * @param withoutConfirmation if given, the resource is deleted without any confirmation + */ +function deleteResource(nameOfResource, url, onSuccess, onError, onStart, withoutConfirmation) { + var f = function() { + $.ajax({ + url: url, + type: 'DELETE', + async: true, + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not delete " + nameOfResource, jqXHR, errorThrown); + if (onError) onError(); + }, + success: function(data, textStatus, jqXHR) { + vShowSuccess("Successfully deleted " + nameOfResource); + onSuccess(data, textStatus, jqXHR); + } + }); + }; + if (withoutConfirmation) { + f(); + } else { + vConfirmYesNo("Do you really want to delete " + nameOfResource + "?", f); + } +} + +/** + * Function to create a td with two columns: one for a key and one for a value + * + * Has to be called from a td element: This function uses $(this) to determine the td + * It changes the content of the td to an input field. In case the input was updated, it is POSTed to the given URL + * + * TODO: The editing mode should use x-editable instead of a self-written input handling + * + * @param url the URL to post the key/value pair to + * @param keyName the keyname of the key - used at POST with <keyName>=<newKey> + * @param valueName the keyname of the value - used at POST with <valueName>=<newValue> + * + */ +function vCreateTdClickFunction(url, keyName, valueName) { + var inputId = "thingedit" + Math.floor((Math.random()*100)+1); ; + keyName = keyName || "key"; + valueName = valueName || "value"; + + var f = function(e) { + var input = $("#" + inputId); + if (input.length != 0) { + // input field already there + return; + } + + var td = $(this); + var oldPrefix = td.text(); + var html = "<input id='" + inputId + "' value='" + oldPrefix + "'></input>"; + td.html(html); + + // new field generated, has to be looked up + input = $("#" + inputId); + + input.keydown(function(e) { + if (e.keyCode == 27) { + // ESC key pressed + input.off("blur"); + td.html(oldPrefix); + } else if (e.keyCode == 13) { + // enter key pressed + input.trigger("blur"); + } + }); + + input.focus(); + + input.on("blur", function() { + var newPrefix = input.val(); + if (newPrefix == oldPrefix) { + td.html(newPrefix); + } else { + var namespace = td.next().text(); + newPrefixEscaped = escape(newPrefix); + namespaceEscaped = escape(namespace); + var data = keyName + "=" + newPrefixEscaped + "&" + valueName + "=" + namespaceEscaped; + $.ajax({ + url: url, + type: "POST", + async: false, + data: data, + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not update data", jqXHR, errorThrown); + input.focus(); + }, + success: function(data, textSTatus, jqXHR) { + vShowSuccess("Successfully updated data"); + td.html(newPrefix); + } + }); + } + }); + }; + + return f; +} + +/** + * This function is also availble at winery-support-common.js + * This function here is due to legacy reasons and all callers should move to winery-support-common.js + * + * @param qname a QName in the form {namespace}localname + * @return { namespace: namespace, localname: localname } + */ +function getNamespaceAndLocalNameFromQName(qname) { + var i = qname.indexOf("}"); + var res = { + namespace : qname.substr(1,i-1), + localname : qname.substr(i+1) + }; + return res; +} + +/** + * Converts a QName of the form {namespace}id to the form prefix:id + * with a lookup of the prefix in the element + * + * returns wrong data if prefix wsa not found + */ +function getQNameOutOfFullQName(fullQname, element) { + var nsAndId = getNamespaceAndLocalNameFromQName(fullQname); + var prefix = element.lookupPrefix(nsAndId.namespace); + var qname = prefix + ":" + nsAndId.localname; + return qname; +} + +/** + * Converts a QName of the form prefix:id to the form {namespace}id + * with a lookup of the prefix in the element + * + * Currently not used anywhere + */ +function getFullQNameOutOfQName(qname, element) { + var i = qname.indexOf(":"); + var prefix = qname.substr(0,i-1); + var localname = qname.substr(i+1); + var namespace = element.lookupPrefix(prefix); + return "{" + namespace + "}" + localname; +} + +function encodeID(id) { + // the URL sent to the server should be the encoded id + id = encodeURIComponent(id); + // therefore, we have to encode it twice + id = encodeURIComponent(id); + return id; +} + +// URLs from QName are provided by winery-support-common.js +function makeArtifactTemplateURL(repoURL, namespace, id) { + return repoURL + "/artifacttemplates/" + encodeID(namespace) + "/" + encodeID(id) + "/"; +} + +if (!window.winery) window.winery = {}; +window.winery.BOOTSTRAP_ANIMATION_DURATION = 400; diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-sugiyamaLayouter.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-sugiyamaLayouter.js new file mode 100644 index 0000000..fa63f56 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-sugiyamaLayouter.js @@ -0,0 +1,634 @@ +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Kálmán Képes - initial API and implementation and/or initial documentation + *******************************************************************************/ + +/** + * Implements abstractLayouter#layout and uses the Sugiyama method to apply a + * layered layout for TOSCA Topology Template graphs. The Sugiyama method + * proposes four steps to apply the layout:<br> + * First: Remove the cycles temporarly of the given graph by finding edges that + * are members of a cycle and reverse them<br> + * Second: Layer the nodes by using DFS/BFS and reduce the "height"/"width" of + * the graph<br> + * Third: Reduce the crossings of edges, by applying a suitable heuristic <br> + * Fourth: Assign the coordinates for the nodes/edges + * + * @param {Array} + * nodeTemplates, an array with div-elements of class + * ".NodeTemplateShape" + */ +define(function() { + + var module = { + layout: layout + }; + return module; + + function layout(nodeTemplates) { + + + // Step 1: "Remove" Cycles + var hasCycle = _hasCycle(nodeTemplates); + var complementEdgesOfSubgraph = null; + if (hasCycle){ + var edgesOfAcyclicSubgraph = _computeAcyclicSubgraph(nodeTemplates); + complementEdgesOfSubgraph = _computeEdgeComplement(nodeTemplates, + edgesOfAcyclicSubgraph); + _reverseEdges(complementEdgesOfSubgraph); + } + + // Step 2: Assign Layers + _assignLayers(nodeTemplates); + + // Step 3: reduce crossing of edges + _reduceCrossing(nodeTemplates); + + // Step 4: assign coordinates + _assignCoordinates(nodeTemplates); + + if(hasCycle){ + _reverseEdges(complementEdgesOfSubgraph); + } + + jsPlumb.repaintEverything(); + // if used twice, edges are drawn straight and not random + jsPlumb.repaintEverything(); + + // cleanup -> remote internal data + $(nodeTemplates).removeAttr("layer"); + $(nodeTemplates).removeAttr("layerPos"); + + } + + + function callsOnLayout(nodeTemplates) { + // TODO start sugiyama layout method with special handling of calls-on + // relations + } + + /** + * Returns true if the given graph contains a cycle + * @param nodeTemplates the vertices of the graph, where cycle detection should be performed. The vertices should be an array of NodeTemplates + * @returns {Boolean} true iff a cycle was detected in the graph, else false + */ + function _hasCycle(nodeTemplates) { + for(var i = 0; i < nodeTemplates.length;i++){ + nodeTemplates[i].cycleMark = "notStarted"; + } + + var hasCycle = false; + var sources = _returnSources(nodeTemplates); + + for(var i = 0; i < sources.length; i++){ + _cycleSearch(sources[i],hasCycle); + if(hasCycle){ + // remove marks + $(nodeTemplates).removeAttr("cycleMark"); + return hasCycle; + } + } + + return hasCycle; + } + + var tarjanCounter = 1; + + /** + * Detects cycles which are reachable by the given NodeTemplate of a graph. Algorithm is based on DFS + * @param nodeTemplate the starting NodeTemplate for the DFS + * @param hasCycle boolean variable to propagate if a cycle was found, init value should be 'false' + */ + function _cycleSearch(nodeTemplate,hasCycle){ + if(nodeTemplate.cycleMark = "inWork"){ + hasCycle =true; + } else if(nodeTemplate.cycleMark ="notStarted") { + nodeTemplate.cycleMark ="inWork"; + var successors = _returnSuccessors(nodeTemplate); + for(var i = 0 ; i < successors.length; i++){ + _cycleSearch(successors[i]); + } + nodeTemplate.cycleMark ="finished"; + } + + } + + /** + * Calculates strongly connected components according to Tarjans algorithm + * @param nodeTemplate a NodeTemplate that is used to begin the algorithm on + * @param foundCycle boolean variable to propagate if a cycle was found, init value should be 'false' + */ + function _findComponent(nodeTemplate, foundCycle){ + if(nodeTemplate.cycleMark == "inWork"){ + foundCycle = true; + } else if(nodeTemplate.cycleMark == "notStarted"){ + nodeTemplate.cycleMark = "inWork"; + nodeTemplate.dfsNum = tarjanCounter; + nodeTemplate.compNum = tarjanCounter; + tarjanCounter++; + var successors = _returnSuccessors(nodeTemplate); + for(var i = 0; i < successors.length;i++){ + var successor = successors[i]; + if(successor.cycleMark != "finished"){ + _findComponent(successor,foundCycle); + if(successor.compNum < nodeTemplate.compNum){ + nodeTemplate.compNum = successor.compNum; + } + } + } + nodeTemplate.cycleMark = "finished"; + } + } + + /** + * Assigns x/y-Coordinates to the given NodeTemplates. It is assumed that the + * NodeTemplates are already layered, e.g. nodeTemplate.layer, + * nodeTemplate.layerPos is set. + * + * @param nodeTemplates + * the nodetemplate to set it's x/y coordinates + */ + function _assignCoordinates(nodeTemplates) { + // get the layers out of the graph and the number of layers + var highestLayerNumber = _returnHighestLayerNumber(nodeTemplates); + var layerMap = {}; + for ( var i = 1; i <= highestLayerNumber; i++) { + layerMap[i.toString()] = _returnLayer(nodeTemplates, i); + } + + // determine the layer with the greatest width, width = {max(|U|) : U is + // layer of V} + var widestLayerWidth = -1; + for (layer in layerMap) { + if (layer.length > widestLayerWidth) { + widestLayerWidth = layer.length; + } + } + + // get maximum node width + var maxNodeWidth = _getMaxWidthOfNodeArray(nodeTemplates); + + // get maximum node height + var maxNodeHeight = _getMaxHeightOfNodeArray(nodeTemplates); + + var editorArea = $("#editorArea")[0]; + var height = editorArea.clientHeight; + var width = editorArea.clientWidth; + + // TODO fine tuning or change the algorithm to handle callsOn Relations + // independently, e.g. draw it in an own tree/list/graph + // distance in height between nodes + var prettyHeight = 75; + for ( var i = highestLayerNumber; i > 0; i--) { + var layer = _returnLayer(nodeTemplates, i); + // distance in width between nodes + var prettyWidth = 75; + for ( var nodeIndex = 0; nodeIndex < layer.length; nodeIndex++) { + var layerNumber = i; + var layerPos = layer[nodeIndex].layerPos; + layer[nodeIndex].style.left = ((layerPos) * maxNodeWidth + prettyWidth) + + "px"; + layer[nodeIndex].style.top = ((highestLayerNumber - layerNumber) + * maxNodeHeight + prettyHeight) + + "px" + prettyWidth += 50; + } + prettyHeight += 100; + } + } + + /** + * Returns the height of the nodeTemplate with the highest height + * @param nodeTemplates an array of NodeTemplates + * @returns {Number} the maximum height of the given NodeTemplates + */ + function _getMaxHeightOfNodeArray(nodeTemplates) { + var maxHeight = -1; + for ( var i = 0; i < nodeTemplates.length; i++) { + // TODO clientHeight responds to the complete page, not only the + // nodetemplate + if (maxHeight < nodeTemplates[i].clientHeight) { + maxHeight = nodeTemplates[i].clientHeight; + } + } + return maxHeight; + } + + /** + * Returns the width of the nodeTemplate with the highest width + * @param nodeTemplates an array of NodeTemplates + * @returns {Number} the maximum width of the given NodeTemplates + */ + function _getMaxWidthOfNodeArray(nodeTemplates) { + var maxWidth = -1; + for ( var i = 0; i < nodeTemplates.length; i++) { + // TODO clientWidth responds to the complete page, not only the + // nodetemplate + if (maxWidth < nodeTemplates[i].clientWidth) { + maxWidth = nodeTemplates[i].clientWidth; + } + } + return maxWidth; + } + + /** + * Reduces crossing between the already layered Graph, given as a Array of NodeTemplates + * @param nodeTemplates the vertices of the layered graph, to reduce crossing between the layers + */ + function _reduceCrossing(nodeTemplates) { + // initialize arbitrary orderings inside layers + _initLayerOrder(nodeTemplates); + var maxlayerlevel = _returnHighestLayerNumber(nodeTemplates); + // variable to reduce crossings of layer-pairs one by one + var layerCount = 1; + // variable to check if some change was made + var changed = true; + while (layerCount < maxlayerlevel | changed) { + // the algorithm should run until no position changes are made + if (layerCount == maxlayerlevel) { + layerCount = 1; + } + changed = false; + + var layer = _returnLayer(nodeTemplates, layerCount + 1); + + // INFO: This piece of code reduces the corssing according to the + // barycenter heuristic + for ( var i = 0; i < layer.length; i++) { + // deg(u), where u = layer[i] + var deg_u = _returnIncomingEdges(layer[i]).length + + _returnOutgoingEdges(layer[i]).length; + + // sum of layerpos(v), for ever v where (u,v) exists in the graph + var outgoingEdges = _returnOutgoingEdges(layer[i]); + var sum = 0.0; + for ( var j = 0; j < outgoingEdges.length; j++) { + var node_v = outgoingEdges[j].target; + sum = sum + node_v.layerPos; + } + + // barycenter heuristic + var bary_u = 1 / deg_u * sum; + layer[i].barycenter = bary_u; + } + + // sorting the nodetemplates in the layer accorindg to the barycenter + layer.sort(function(a, b) { + return a.barycenter - b.barycenter + }); + + // rearrange positions in layer + for ( var i = 0; i < layer.length; i++) { + if (layer[i].layerPos != i + 1) { + // checks whether some change of the position is made + changed = true; + } + layer[i].layerPos = i + 1; + } + + // get ready for the next layer + layerCount++; + } + } + + /** + * Returns an array of NodeTemplates which are in the same Layer (nodeTemplate.layer) + */ + function _returnLayer(nodeTemplates, layerNumber) { + var layer = new Array(); + for ( var i = 0; i < nodeTemplates.length; i++) { + if ("layer" in nodeTemplates[i] + && nodeTemplates[i].layer == layerNumber) { + layer.push(nodeTemplates[i]); + } + } + layer.sort(function(a, b) { + return a.layerPos - b.layerPos; + }); + return layer; + } + + /** + * Returns the number of the highest layer of the given layered graph + * @param nodeTemplates the vertices of the layered graph as an array of NodeTemplates + * @returns {Number} the highest layer number + */ + function _returnHighestLayerNumber(nodeTemplates) { + var layerNumber = -1; + for ( var i = 0; i < nodeTemplates.length; i++) { + if (!"layer" in nodeTemplates[i]) { + // some node has no "layer" property defined <=> graph isn't layered + return -1; + } else { + if (nodeTemplates[i].layer > layerNumber) { + layerNumber = nodeTemplates[i].layer; + } + } + } + return layerNumber; + } + + /** + * Initializes a layering order in each layer of the given graph + * @param nodeTemplates the vertices of the graph to layer, as an array of NodeTemplates + */ + function _initLayerOrder(nodeTemplates) { + // "map" := layerNumber : ordernumber + var orderingCount = {}; + // init with 1 : 1 + orderingCount["1"] = 1; + for ( var i = 0; i < nodeTemplates.length; i++) { + // get layer number of the nodetemplate + var layerNumber = nodeTemplates[i].layer; + // is the layer already in the map ? + if (layerNumber.toString() in orderingCount) { + // if yes, set the pos inside layer and increment for next node + nodeTemplates[i].layerPos = orderingCount[layerNumber.toString()]; + orderingCount[layerNumber.toString()] = orderingCount[layerNumber + .toString()] + 1; + } else { + // if not, set the pos to 1 and add layer to map with count 2 + nodeTemplates[i].layerPos = 1; + // init pos for next node + orderingCount[layerNumber.toString()] = 2; + } + } + } + + /** + * Assigns layer numbers to the given graph + * @param nodeTemplates the vertices of the graph to assign layers to, as an array of NodeTemplates + */ + function _assignLayers(nodeTemplates) { + var sinks = {}; + for ( var i = 0; i < nodeTemplates.length; i++) { + if (_returnOutgoingEdges(nodeTemplates[i]).length == 0) { + sinks[i] = nodeTemplates[i]; + } + } + + // array of DIV delements + var nodesToCompute = new Array(); + + for ( var sink in sinks) { + // set layer level for the sinks and add the nodes which are reachable + // from there + sinks[sink].layer = 1; + var inEdges = _returnIncomingEdges(sinks[sink]); + for ( var i = 0; i < inEdges.length; i++) { + nodesToCompute.push(inEdges[i].source); + } + } + + // set other layer levels + while (nodesToCompute.length != 0) { + var node = nodesToCompute.shift(); + var layerNumber = _returnLayerNumber(node); + if (layerNumber == -1) { + nodesToCompute.push(node); + } else { + node.layer = layerNumber; + // add ingoing nodes to compute + var edgesToNodes = _returnIncomingEdges(node); + for ( var i = 0; i < edgesToNodes.length; i++) { + nodesToCompute.push(edgesToNodes[i].source); + } + } + } + } + + /** + * Returns the number of the layer a NodeTemplate belongs to + * @param nodeTemplate a NodeTemplate with already set layer position + * @returns {Number} the number of the layer the NodeTemplate belongs to + */ + function _returnLayerNumber(nodeTemplate) { + var layerNumber = -1; + var outgoingEdges = _returnOutgoingEdges(nodeTemplate); + for ( var i = 0; i < outgoingEdges.length; i++) { + // we will see if it works here + var successorNode = outgoingEdges[i].target; + if ("layer" in successorNode) { + if (successorNode.layer + 1 >= layerNumber) { + layerNumber = successorNode.layer + 1; + } + } else { + layerNumber = -1; + break; + } + } + return layerNumber; + } + + /** + * Returns all jsPlumb#Connection edges of the graph represented by the + * nodeTemplates array of NodeTemplateShape which aren't referenced in the + * subgraphEdges array of type jsPlumb#Connection. + * + * @param {Object} + * nodeTemplates, array of NodeTemplateShape + * @param {Object} + * subgraphEdges, array of jsPlumb#Connection + * @return {Object} array of jsPlumb#Connection which contains edges that aren't + * in the subgraph + */ + function _computeEdgeComplement(nodeTemplates, subgraphEdges) { + var edgeSet = {}; + for ( var i = 0; i < nodeTemplates.length; i++) { + var incomingEdges = _returnIncomingEdges(nodeTemplates[i]); + var outgoingEdges = _returnOutgoingEdges(nodeTemplates[i]); + for ( var incomingEdgeIndex = 0; incomingEdgeIndex < incomingEdges.length; incomingEdgeIndex++) { + var inSubgraph = false; + for ( var subgraphEdgeIndex = 0; subgraphEdgeIndex < subgraphEdges.length; subgraphEdgeIndex++) { + if (incomingEdges[incomingEdgeIndex] === subgraphEdges[subgraphEdgeIndex]) { + inSubgraph = true; + break; + } + } + if (!inSubgraph) { + edgeSet[incomingEdges[incomingEdgeIndex]] = incomingEdges[incomingEdgeIndex]; + } + } + for ( var outgoingEdgeIndex = 0; outgoingEdgeIndex < outgoingEdges.length; outgoingEdgeIndex++) { + var inSubgraph = false; + for ( var subgraphEdgeIndex = 0; subgraphEdgeIndex < subgraphEdges.length; subgraphEdgeIndex++) { + if (outgoingEdges[outgoingEdgeIndex] === subgraphEdges[subgraphEdgeIndex]) { + inSubgraph = true; + break; + } + } + if (!inSubgraph) { + edgeSet[outgoingEdges[outgoingEdgeIndex]] = outgoingEdges[outgoingEdgeIndex]; + } + } + } + // transform edge-set to array + var edgeArray = new Array(); + for (edge in edgeSet) { + edgeArray.push(edgeSet[edge]); + } + return edgeArray; + } + + /** + * Returns a list of edges which represent an acyclic subgraph of the given + * nodes. The graph is at least of size 1/2*|E|, which can be troublesome in + * some situations. This means the graph could be layouted only on the "half" of + * it. + * + * Info: There are more sophisticated methods that guarantee to produce bigger + * subgraphs, see "Drawing Graphs: Methods and Models". But in this state it is + * a huge undertake, cause Oryx doesn't seem to have any basic graph algorithm + * shipped, like DFS etc.(correct me if i'm wrong, please). + * + * @param {Object} + * nodes of a graph, which are NodeTemplatesShapes + * @return [Object] edges of the contained acyclic subgraph, which are + * jsPlumb#Connection Objects + * @see "Drawing Graphs: Methods and Models", p. 91 + */ + function _computeAcyclicSubgraph(nodeTemplates) { + var subgraphEdges = []; + for ( var i = 0; i < nodeTemplates.length; i++) { + if (_returnOutgoingEdges(nodeTemplates[i]).length >= _returnIncomingEdges(nodeTemplates[i]).length) { + for ( var j = 0; j < _returnOutgoingEdges(nodeTemplates[i]).length; j++) { + subgraphEdges.push(_returnOutgoingEdges(nodeTemplates[i])[j]); + } + } else { + for ( var j = 0; j < _returnIncomingEdges(nodeTemplates[i]).length; j++) { + subgraphEdges.push(_returnIncomingEdges(nodeTemplates[i])[j]); + } + } + } + var filteredArray = subgraphEdges.filter(function(elem, pos) { + return subgraphEdges.indexOf(elem) == pos; + }); + return filteredArray; + } + + /** + * Returns relations having the given NodeTemplate as target + * + * @param nodeTemplate + * a nodeTemplateShape + * @returns Returns an array containing jsPlumb#Connection objects representing + * relations + */ + function _returnIncomingEdges(nodeTemplate) { + var edgeArray = new Array(); + jsPlumb.select().each( + function(connection) { + if (connection.targetId === nodeTemplate.id) { + /*if (connection.getType().length != 0) { + for ( var i = 0; i < connection.getType().length; i++) { + if (connection.getType()[i].toLowerCase().indexOf( + "hostedon") !== -1) { + edgeArray.push(connection); + } + } + } else { + edgeArray.push(connection); + }*/ + edgeArray.push(connection); + + } + }); + return edgeArray; + } + + /** + * Returns relations having the given NodeTemplate as source + * + * @param nodeTemplate + * a nodeTemplateShape + * @returns Returns an array containing jsPlumb#Connections objects representing + * relations + */ + function _returnOutgoingEdges(nodeTemplate) { + var edgeArray = new Array(); + jsPlumb.select().each( + function(connection) { + if (connection.sourceId === nodeTemplate.id) { + /*if (connection.getType().length != 0) { + for ( var i = 0; i < connection.getType().length; i++) { + if (connection.getType()[i].toLowerCase().indexOf( + "hostedon") !== -1) { + edgeArray.push(connection); + } + } + } else { + edgeArray.push(connection); + }*/ + edgeArray.push(connection); + } + }); + return edgeArray; + } + + /** + * Returns the NodeTemplates which can be reached by a outgoing edges of the given NodeTemplate + * @param nodeTemplate the NodeTemplate whose Succesors should be calculated + * @returns {Array} an array of NodeTemplates which are successors of the given NodeTemplate, may be empty + */ + function _returnSuccessors(nodeTemplate){ + var nodeArray = new Array(); + var outgoingEdges = _returnOutgoingEdges(nodeTemplate); + for(var i =0; i < outgoingEdges.length; i++){ + nodeArray.push(outgoingEdges[i].target); + } + return nodeArray; + } + + /** + * Returns all sources of the given graph + * @param nodeTemplates vertices of the graph, as an array of NodeTemplates + * @returns {Array} an Array of NodeTemplates + */ + function _returnSources(nodeTemplates){ + var sourceArray = new Array(); + for(var i = 0; i < nodeTemplates.length; i++){ + if(_returnIncomingEdges(nodeTemplates[i]) == 0){ + sourceArray.push(nodeTemplates[i]); + } + } + return sourceArray; + } + + /** + * Reverses a relationshipTemplate + * + * @param relationshipTemplate + * as we don't have relationshiptemplates modelled here we expect + * jsPlumb#Connection objects + */ + function _reverseEdge(relationshipTemplate) { + // this seems to work + var source = relationshipTemplate["source"]; + var sourceId = relationshipTemplate["sourceId"]; + var target = relationshipTemplate["target"]; + var targetId = relationshipTemplate["targetId"]; + + relationshipTemplate["source"] = target; + relationshipTemplate["sourceId"] = targetId; + relationshipTemplate["target"] = source; + relationshipTemplate["targetId"] = sourceId; + } + + /** + * Reverses relationshipTemplates + * + * @param relationshipTemplates + */ + function _reverseEdges(relationshipTemplates) { + for ( var i = 0; i < relationshipTemplates.length; i++) { + _reverseEdge(relationshipTemplates[i]); + } + } +});
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-support-common.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-support-common.js new file mode 100644 index 0000000..4e87f22 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-support-common.js @@ -0,0 +1,330 @@ +/******************************************************************************* + * Copyright (c) 2012-2014 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ + +/** + * Functions copied from winery-common.js to replace it in the long term + * + * Shared between topology modeler and repository + */ +define([], function() { + var xmlParser = new DOMParser(); + var VALUE_OF_NONE_CHOOSEN = "(none)"; // constant to indicate that nothing is chosen in a select2 + + return { + encodeId: encodeId, + getNamespaceAndLocalNameFromQName: getNamespaceAndLocalNameFromQName, + replaceDialogShownHookForOrionUpdate: replaceDialogShownHookForOrionUpdate, + writeCollectionDefinedByATextArea: writeCollectionDefinedByATextArea, + getDocument: getDocument, + + getURLFragmentOutOfFullQName: getURLFragmentOutOfFullQName, + makeArtifactTypeURLFromQName: makeArtifactTypeURLFromQName, + makeNodeTypeURLFromQName: makeNodeTypeURLFromQName, + makeRelationshipTypeURLFromQName: makeRelationshipTypeURLFromQName, + makeRelationshipTypeURLFromNSAndLocalName: makeRelationshipTypeURLFromNSAndLocalName, + + qname2href: qname2href, + + fetchSelect2DataAndInitSelect2: fetchSelect2DataAndInitSelect2, + removeItemFromSelect2Field: removeItemFromSelect2Field, + + checkXMLValidityAndShowErrorIfInvalid: checkXMLValidityAndShowErrorIfInvalid, + synchronizeNameAndType: synchronizeNameAndType, + + VALUE_OF_NONE_CHOOSEN: VALUE_OF_NONE_CHOOSEN + }; + + /** + * OriginalName: encodeID + */ + function encodeId(id) { + // the URL sent to the server should be the encoded id + id = encodeURIComponent(id); + // therefore, we have to encode it twice + id = encodeURIComponent(id); + return id; + } + + /** + * @param qname a QName in the form {namespace}localname + * @return { namespace: namespace, localname: localname } + */ + function getNamespaceAndLocalNameFromQName(qname) { + var i = qname.indexOf("}"); + var res = { + namespace : qname.substr(1,i-1), + localname : qname.substr(i+1) + }; + return res; + } + + /** + * Orion does not update content if field not fully shown + * therefore, we hook in into the "shown" event + */ + function replaceDialogShownHookForOrionUpdate(diag, orionAreaId, content) { + diag.off("shown.bs.modal"); + diag.on("shown.bs.modal", function() { + var area = window.winery.orionareas[orionAreaId]; + area.editor.setText(content); + area.fixEditorHeight(); + }); + } + + function getURLFragmentOutOfNSAndLocalName(nsAndLocalName) { + var res; + res = encodeID(nsAndLocalName.namespace); + res = res + "/"; + res = res + encodeID(nsAndLocalName.localname); + return res; + } + + /** + * Extracts an URL fragment of the form <encoded namespace>/<encoded id> out of a full QName + * + * @param qname a QName in the form {namespace}localname + */ + function getURLFragmentOutOfFullQName(qname) { + var d = getNamespaceAndLocalNameFromQName(qname); + return getURLFragmentOutOfNSAndLocalName(d); + } + + /** + * @param w the XMLwriter + * @param elementSet the set of HTML elements to write + * @param elementName the name of the wrapper element (e.g., "Requirements", "Policies") + */ + function writeCollectionDefinedByATextArea(w, elementSet, elementName) { + if (elementSet.length !== 0) { + w.writeStartElement(elementName); + elementSet.each(function(i, element) { + // XML contains element completely + // we do not have to parse reqorcap.children("div.id").children("span.id").text() or the span.name + var text = $(element).children("textarea").val(); + w.writeXML(text); + }); + w.writeEndElement(); + } + } + + function makeArtifactTypeURLFromQName(repoURL, qname) { + return repoURL + "/artifacttypes/" + getURLFragmentOutOfFullQName(qname) + "/"; + } + + function makeNodeTypeURLFromQName(repoURL, qname) { + return repoURL + "/nodetypes/" + getURLFragmentOutOfFullQName(qname) + "/"; + } + + function makeRelationshipTypeURLFromQName(repoURL, qname) { + return repoURL + "/relationshiptypes/" + getURLFragmentOutOfFullQName(qname) + "/"; + } + + function makeRelationshipTypeURLFromNSAndLocalName(repoURL, nsAndLocalName) { + return repoURL + "/relationshiptypes/" + getURLFragmentOutOfNSAndLocalName(nsAndLocalName) + "/"; + } + + /** + * functionality similar to org.eclipse.winery.common.Util.qname2href(String, Class<? extends TExtensibleElements>, QName) + */ + function qname2href(repositoryUrl, componentPathFragment, qname) { + var nsAndId = getNamespaceAndLocalNameFromQName(qname); + var absoluteURL = repositoryUrl + "/" + componentPathFragment + "/" + getURLFragmentOutOfNSAndLocalName(nsAndId); + var res = "<a target=\"_blank\" data-qname=\"" + qname + "\" href=\"" + absoluteURL + "\">" + nsAndId.localname + "</a>"; + return res; + } + + /** + * Inspired by + * + * @param field is the jquery field + * @param id_to_remove the id to remove + */ + function removeItemFromSelect2Field(field, id_to_remove) { + // nothing can be done currently + // see https://github.com/ivaynberg/select2/issues/535#issuecomment-30210641 for a disucssion + vShowNotification("The select field shows stale data. Refresh the page to get rid of that.") + } + + /** + * Fetches select2 data from the given URL and initializes the field provided by the fieldId + * + * Calls vShowError if something went wrong + * + * @param onSuccess (optional) + * @param allowAdditions (optional) if set to true, select2 is initalized with the functionality to allow additions during the search + */ + function fetchSelect2DataAndInitSelect2(fieldId, url, onSuccess, allowAdditions) { + $.ajax({ + url: url, + dataType: "json" + }).done(function (result) { + var params = {"data": result}; + if (typeof allowAdditions === "boolean") { + params.createSearchChoice = function(term) { + // enables creation of new namespaces + return {id:term, text:term}; + } + } + + // init select2 and select first item + $("#" + fieldId).select2(params); + if (result.length === 0) { + $("#" + fieldId).select2("val", null); + } else { + $("#" + fieldId).select2("val", result[0].id); + } + + if (typeof onSuccess === "function") { + onSuccess(); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not fetch select2 data from " + url, jqXHR, errorThrown); + }); + + } + + + function getDocument(xmlString) { + var xmlDoc = xmlParser.parseFromString(xmlString, "text/xml"); + return xmlDoc; + } + + /** + * Checks given XML string for validity. If it's invalid, an error message is shown + * Relies on the current browser's XML handling returning a HTML document if something went wrong during parsing + * + * @return XMLdocument if XML is valid, false otherwise + */ + function checkXMLValidityAndShowErrorIfInvalid(xmlString) { + var doc = getDocument(xmlString); + var errorMsg = ""; + if (doc.firstChild.localName == "html") { + errorMsg = new XMLSerializer().serializeToString(doc); + } else { + // at Chrome, the error may be nested in the XML + + // quick hack, only "xhtml" is quered + function nsResolover(x) { + return "http://www.w3.org/1999/xhtml"; + } + + var element = doc.evaluate( '//x:parsererror', doc, nsResolover, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; + if (element !== null) { + errorMsg = new XMLSerializer().serializeToString(element); + } + } + + if (errorMsg !== "") { + vShowError(errorMsg); + return false; + } else { + return doc; + } + } + + /** + * Updates the XML in the orion editor based on the values given in the input fields. + * Shows error if XML is invalid + * + * Works only with SELECT2 fields + * + * @param idPrefix: (new|edit)${shortName}, derived names: [idPrefix]Name, [idPrefix]Id, Orion[idPrefix]XML + * @param hasIdField: whether the Id should be read and written + * @param selectFields: array of {attribute, fieldSuffix}, where attribute is a name of an attribute having a QName. + * Each select box is determined by #[idPrefix][fieldSuffix]. + * The select box content is converted to a QName and the result is written to the attribute [name] + * Default: {attribute: "type", fieldSuffix: "Type"} + * + * @return false if XML is invalid, true: an object if id/name/attribute1/attribute2/... (qname + attribute1FullQName: qname object)/xml (to be used in tmpl-${cssClassPrefix}) + * {id:id, name:name, type: "ns5:type", typeFullQName: "{http://www.example.org}type"} + */ + function synchronizeNameAndType(idPrefix, hasIdField, selectFields) { + if (typeof hasIdField === undefined) { + hasIdField = true; + } + if (typeof selectFields === undefined) { + selectFields = [{attribute: "type", fieldSuffix: "Type"}]; + } + + + var val = window.winery.orionareas["Orion" + idPrefix + "XML"].editor.getText(); + var xmlDoc = checkXMLValidityAndShowErrorIfInvalid(val); + if (xmlDoc) { + // handle name + var name = $("#" + idPrefix + "Name").val(); + // initialize result object + var res = { + name: name + }; + xmlDoc.firstChild.setAttribute("name", name); + + // write id and name to XML + if (hasIdField) { + var id = $("#" + idPrefix + "Id").val(); + if (!id) { + // TODO a checking should be done if the id exists + // probably not here, but at caller's side + id = name; + } + xmlDoc.firstChild.setAttribute("id", id); + res.id = id; + } + + // write each selectField to xml + // for that, we have to determine the QName + $(selectFields).each(function(i, m) { + var content = $("#" + idPrefix + m.fieldSuffix).select2("val"); + + if (content == VALUE_OF_NONE_CHOOSEN) { + // if nothing is chosen do not put it into the result + return; + } + + // determine qname of type + //getQNameOutOfFullQName(type, xmlDoc.firstChild) does not always work as xmlDoc.firstChild does not have ALL *available* namespace prefixes + var typeNSAndId = getNamespaceAndLocalNameFromQName(content); + var prefix = xmlDoc.firstChild.lookupPrefix(typeNSAndId.namespace); + if (!prefix) { + // we have to ask the repo for a prefix + $.ajax({ + type: "GET", + async: false, + "url": winery.repositoryURL + "/admin/namespaces/" + encodeID(typeNSAndId.namespace), + dataType: "text", + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not determine prefix", jqXHR, errorThrown); + }, + success: function(resData, textStatus, jqXHR) { + prefix = resData; + } + }); + // new prefix fetched, xmlns attribute has to be written + xmlDoc.firstChild.setAttribute("xmlns:" + prefix, typeNSAndId.namespace); + } + var qname = prefix + ":" + typeNSAndId.localname; + res[m.attribute] = qname; + res[m.attribute + "FullQName"] = typeNSAndId; + xmlDoc.firstChild.setAttribute(m.attribute, qname); + }); + + var xml = new XMLSerializer().serializeToString(xmlDoc); + res.xml = xml; + + return res; + } else { + return false; + } + } + + + } +); diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologycompletion.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologycompletion.js new file mode 100644 index 0000000..6ab66e8 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologycompletion.js @@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +define(function() { + + /** + * This module handles a click on "Complete Topology" in the enterTopologyCompletionInformation dialog. + */ + var module = { + complete: complete, + restartCompletion: restartCompletion + }; + return module; + + /** + * This function start the topology completion. With information from the + * PolicyInformationDialog the topologyCompletion.jsp is called which will invoke the + * Topology Completion java component. The parameters determine if the current topology + * will be overwritten or if a new topology is created. + * + * @param (String) overwriteTopology + * determines how to save the topology. If true, the current topology is overwritten. + * @param (Boolean) openInNewWindow + * determines if the result is opened in a new window + * @param (String) topologyName + * the name of the new topology + * @param (String) topologyNamespace + * the namespace of the new topology + * @param (Boolean) stepByStep + * if true, the completion will be processed step by step + * @param (String) repositoryURL + * the URL to the repository the topology is saved to + * @param (String) toscaNamespace + * the namespace URL of TOSCA + * @param (String) wineryNamespace + * the namespace URL of winery + * @param (String) serviceTemplateName + * the name of the service template containing the displayed topology template + * @param (String) topologyTemplateURL + * the URL to the displayed topology template + */ + function complete(overwriteTopology, openInNewWindow, topologyName, topologyNamespace, stepByStep, repositoryURL, serviceTemplateName, topologyTemplateURL) { + + // if the user wants to create a new topology, a post call is sent to the repository adding + // the new ServiceTemplate + if (!overwriteTopology) { + var dataToSend = "name=" + topologyName + "&namespace=" + topologyNamespace; + var url = repositoryURL + "/servicetemplates/"; + $.ajax( + { + type: "POST", + async: false, + url: url, + "data": dataToSend, + dataType: "text", + error: function(jqXHR, textStatus, errorThrown) { + vShowAJAXError("Could not add Service Template."); + } + } + ); + } + + var topology; + require(["winery-topologymodeler-AMD"], function(wt) { + topology = wt.getTopologyTemplateAsXML(true); + + // call to the completion JSP which will call the java component + $.post("jsp/topologyCompletion/topologyCompletion.jsp", {topology: topology, stName: serviceTemplateName, templateURL: topologyTemplateURL, overwriteTopology: overwriteTopology, topologyName: topologyName, topologyNamespace: topologyNamespace, repositoryURL: repositoryURL, stepByStep: stepByStep, openInNewWindow: openInNewWindow, restarted: "false"}, + /** + * Callback function which will either open a new window if a new topology was created or + * refresh the current browser window + * + * @param (String) data + * the answer of the post call + */ + function(data){ + // checks the message returned by the CompletionInterface + if (data.indexOf("topologyComplete") != -1) { + vShowSuccess('The topology is already complete.'); + } else if (data.indexOf("failure") != -1) { + vShowError(data); + } else if (data.indexOf("userTopologySelection") != -1) { + $(chooseTopologyDiag[0].children[0].children[0].children[1]).html(data); + window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION); + chooseTopologyDiag.modal("show"); + } else if (data.indexOf("topologyComplete") == -1 && data.indexOf("userTopologySelection") == -1 && data.indexOf("userInteraction") != -1) { + $(chooseRelationshipTemplateDiag[0].children[0].children[0].children[1]).html(data); + window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION); + chooseRelationshipTemplateDiag.modal("show"); + } else if (data.indexOf("topologyComplete") == -1 && data.indexOf("stepByStep") != -1) { + $(chooseNodeTemplateDiag[0].children[0].children[0].children[1]).html(data); + window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION); + chooseNodeTemplateDiag.modal("show"); + } else { + if (openInNewWindow) { + // a new topology has been created, open it in a new window + var win=window.open('?repositoryURL=' + repositoryURL + '&ns='+ topologyNamespace + '&id=' + topologyName, '_blank'); + win.focus(); + } else if (overwriteTopology) { + // refresh page + document.location.reload(true); + } + } + } + ); + }); + } + + /** + * This function restarts the topology completion when it has been stopped to get a + * user decision. + * + * @param (String) topology + * the topology as XML string + * @param (String) overwriteTopology + * determines how to save the topology. Can contain the values "createNew" or "overwrite" + * @param (Boolean) openInNewWindow + * determines if the result is opened in a new window + * @param (String) topologyName + * the name of the new topology + * @param (String) topologyNamespace + * the namespace of the new topology + * @param (Boolean) stepByStep + * if true, the completion will be processed step by step + * @param (String) serviceTemplateName + * the name of the service template containing the displayed topology template + * @param (String) topologyTemplateURL + * the URL to the displayed topology template + * @param (String) repositoryURL + * the URL to the repository the topology is saved to + */ + function restartCompletion(topology, overwriteTopology, openInNewWindow, topologyName, topologyNamespace, stepByStep, serviceTemplateName, topologyTemplateURL, repositoryURL) { + + // remove whitespaces in the topology XML string + topology = topology.replace(/\s+/g, ' '); + topology = topology.substr(1); + + // call to the completion JSP which will call the java component + $.post("jsp/topologyCompletion/topologyCompletion.jsp", { topology: topology, stName: serviceTemplateName, templateURL: topologyTemplateURL, overwriteTopology: overwriteTopology, topologyName: topologyName, topologyNamespace: topologyNamespace, repositoryURL: repositoryURL, stepByStep: stepByStep, restarted: "true"}, + /** + * Callback function which will either open a new window if a new topology was created or + * refresh the current browser window + * + * @param (String) data + * the answer of the post call + */ + function(data){ + if (data.indexOf("Successful") == -1 && data.indexOf("stepByStep") == -1) { + $(chooseRelationshipTemplateDiag[0].children[0].children[0].children[1]).html(data); + window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION); + chooseRelationshipTemplateDiag.modal("show"); + } else if (data.indexOf("Successful") == -1 && data.indexOf("stepByStep") != -1) { + $(chooseNodeTemplateDiag[0].children[0].children[0].children[1]).html(data); + window.setTimeout(jsPlumb.repaintEverything, JQUERY_ANIMATION_DURATION); + chooseNodeTemplateDiag.modal("show"); + } else { + if (openInNewWindow) { + // a new topology has been created, open it in a new window + var win = window.open('?repositoryURL=' + "<%=repositoryURL%>" + '&ns='+ topologyNamespace + '&id=' + topologyName, '_blank'); + win.focus(); + } else if (overwriteTopology) { + // refresh page + document.location.reload(true); + } + } + } + ); + } +});
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler-AMD.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler-AMD.js new file mode 100644 index 0000000..a292f43 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler-AMD.js @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) 2012-2015 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + *******************************************************************************/ + +/** + * This file contains supporting functions for the topoplogy modeler + */ +define( + // although XMLWriter ist not an AMD module, requirejs does not complain when loading it + ["winery-support-common", "XMLWriter"], + function (w) { + // has to be consistent with {@link org.eclipse.winery.common.constants.Namespaces} + var TOSCA_NAMESPACE = "http://docs.oasis-open.org/tosca/ns/2011/12"; + var TOSCA_WINERY_EXTENSIONS_NAMESPACE ="http://www.opentosca.org/winery/extensions/tosca/2013/02/12"; + + var topologyTemplateURL; + + var module = { + save: save, + setTopologyTemplateURL: function(url) { + topologyTemplateURL = url; + }, + getTopologyTemplateAsXML: getTopologyTemplateAsXML, + + TOSCA_NAMESPACE: TOSCA_NAMESPACE, + TOSCA_WINERY_EXTENSIONS_NAMESPACE: TOSCA_WINERY_EXTENSIONS_NAMESPACE + }; + return module; + + function writeReqOrCaps(elements, xmlw, globalWrapperElementName, singleElementWrapperName) { + if (elements.length != 0) { + xmlw.writeStartElement(globalWrapperElementName); + + $.each(elements, function(i,e) { + xmlw.writeStartElement(singleElementWrapperName); + e = $(e); + xmlw.writeAttributeString("id", e.children(".id").text()); + xmlw.writeAttributeString("name", e.children(".name").text()); + writeType(xmlw, e.children(".type").children("a").data("qname")); + savePropertiesFromDivToXMLWriter(e.children("div.propertiesContainer"), xmlw); + xmlw.writeEndElement(); + }); + + xmlw.writeEndElement(); + } + + } + + /** + * "doSave" + */ + function save() { + $("#saveBtn").button('loading'); + + $.ajax({ + url: topologyTemplateURL, + type: "PUT", + contentType: 'text/xml', + data: getTopologyTemplateAsXML(false), + success: function(data, textStatus, jqXHR) { + $("#saveBtn").button('reset'); + vShowSuccess("successfully saved."); + }, + error: function(jqXHR, textStatus, errorThrown) { + $("#saveBtn").button('reset'); + vShowAJAXError("Could not save", jqXHR, errorThrown); + } + }); + } + + /** + * Creates an XML String of the modelled topology template. + */ + function getTopologyTemplateAsXML(needsDefinitionsTag) { + + var xmlw = new XMLWriter("utf-8"); + xmlw.writeStartDocument(); + + if (needsDefinitionsTag) { + xmlw.writeStartElement("Definitions"); + xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE); + xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE); + + xmlw.writeStartElement("ServiceTemplate"); + xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE); + xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE); + } + xmlw.writeStartElement("TopologyTemplate"); + xmlw.writeAttributeString("xmlns", TOSCA_NAMESPACE); + xmlw.writeAttributeString("xmlns:winery", TOSCA_WINERY_EXTENSIONS_NAMESPACE); + $("div.NodeTemplateShape").not(".hidden").each (function() { + xmlw.writeStartElement("NodeTemplate"); + + var id = $(this).attr("id"); + + var headerContainer = $(this).children("div.headerContainer"); + var name = headerContainer.children("div.name").text(); + var typeQNameStr = headerContainer.children("span.typeQName").text(); + var minmaxdiv = headerContainer.children("div.minMaxInstances"); + var min = minmaxdiv.children("span.minInstances").text(); + var max = minmaxdiv.children("span.maxInstances").text(); + if (max == "∞") { + max = "unbounded"; + } + var x = $(this).css("left"); + x = x.substring(0, x.indexOf("px")); + var y = $(this).css("top"); + y = y.substring(0, y.indexOf("px")); + + xmlw.writeAttributeString("id", id); + if (name != "") { + xmlw.writeAttributeString("name", name); + } + writeType(xmlw, typeQNameStr); + if (min != "") { + xmlw.writeAttributeString("minInstances", min); + } + if (max != "") { + xmlw.writeAttributeString("maxInstances", max); + } + xmlw.writeAttributeString("winery:x", x); + xmlw.writeAttributeString("winery:y", y); + + /** Properties **/ + savePropertiesFromDivToXMLWriter($(this).children("div.propertiesContainer"), xmlw); + + /** Requirements **/ + writeReqOrCaps( + $(this).children("div.requirementsContainer").children("div.content").children("div.reqorcap"), + xmlw, + "Requirements", + "Requirement"); + + /** Capabilities **/ + writeReqOrCaps( + $(this).children("div.capabilitiesContainer").children("div.content").children("div.reqorcap"), + xmlw, + "Capabilities", + "Capability"); + + /** Policies **/ + w.writeCollectionDefinedByATextArea(xmlw, + $(this).children("div.policiesContainer").children("div.content").children("div.policy"), + "Policies"); + + /** Deployment Artifacts **/ + var das = $(this).children("div.deploymentArtifactsContainer").children("div.content").children("div.deploymentArtifact"); + if (das.length != 0) { + xmlw.writeStartElement("DeploymentArtifacts"); + das.each(function(i,e) { + // the textarea contains a valid deployment artifact xml + var xml = $(e).children("textarea").val(); + xmlw.writeXML(xml); + }); + xmlw.writeEndElement(); + } + + // End: Nodetemplate + xmlw.writeEndElement(); + }); + jsPlumb.select().each(function(connection) { + xmlw.writeStartElement("RelationshipTemplate"); + var id = connection.id; + var typeQNameStr = connection.getType()[0]; + + var connData = winery.connections[id]; + if (!connData) { + vShowError("Error in the internal data structure: Id " + id + " not found"); + return; + } + + xmlw.writeAttributeString("id", connData.id); + if (connData.name != "") { + xmlw.writeAttributeString("name", connData.name); + } + writeType(xmlw, typeQNameStr); + + if (typeof connData.propertiesContainer !== "undefined") { + savePropertiesFromDivToXMLWriter(connData.propertiesContainer, xmlw); + } + + xmlw.writeStartElement("SourceElement"); + if (connData.req) { + // conn starts at a requirement + xmlw.writeAttributeString("ref", connData.req); + } else { + // conn starts at a node template + xmlw.writeAttributeString("ref", connection.sourceId); + } + xmlw.writeEndElement(); + xmlw.writeStartElement("TargetElement"); + if (connData.cap) { + // conn ends at a capability + xmlw.writeAttributeString("ref", connData.cap); + } else { + // conn ends at a node template + xmlw.writeAttributeString("ref", connection.targetId); + } + xmlw.writeEndElement(); + + xmlw.writeEndElement(); + }); + + if (needsDefinitionsTag) { + xmlw.writeEndElement(); + xmlw.writeEndElement(); + } + + xmlw.writeEndDocument(); + + return xmlw.flush(); + } + + function writeQNameAttribute(w, nsPrefix, qnameStr) { + var qname = getQName(qnameStr); + w.writeAttributeString("xmlns:" + nsPrefix, qname.namespace); + w.writeAttributeString("type", nsPrefix + ":" + qname.localName); + } + + function writeType(w, typeQNameStr) { + writeQNameAttribute(w, "ty", typeQNameStr); + } + + } +); + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler.js b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler.js new file mode 100644 index 0000000..cc5c564 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/js/winery-topologymodeler.js @@ -0,0 +1,308 @@ +/******************************************************************************* + * Copyright (c) 2012-2013,2015 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + * Yves Schubert - switch to bootstrap 3 + *******************************************************************************/ + +function getQName(qnameStr) { + var pos = qnameStr.indexOf("}"); + var namespace = qnameStr.substring(1, pos); + var localName = qnameStr.substring(pos+1); + var res = { + "namespace": namespace, + "localName": localName + }; + return res; +} + +/** + * @param attributeName an attribute with a value in the form prefix:localname + * @param xmlElement a DOM element (offering the method lookupNamespaceURI) + * @return { ns: namespace, id: id } + */ +function getNSAndId(attributeName, xmlElement) { + var attributeValue = xmlElement.getAttribute(attributeName); + var i = attributeValue.indexOf(":"); + var prefix = attributeValue.substring(0, i); + var localName = attributeValue.substring(i+1); + var ns = xmlElement.lookupNamespaceURI(prefix); + var res = { + ns : ns, + id: localName + }; + return res; +} + +/** + * @param el a href element + * @param pathComponent the path element "artifacttemplates" or "artifacttypes" + * @param attributeName the name of the attribute to read from the given xmlElement + * @param xmlElement used to resolve a namespace prefix to a full namespace URI + */ +function addHref(el, pathComponent, attributeName, xmlElement) { + var nsAndId = getNSAndId(attributeName, xmlElement); + var loc = winery.repositoryURL + "/" + pathComponent + "/" + encodeID(nsAndId.ns) + "/" + encodeID(nsAndId.id); + el.attr("href", loc); +} + +var currentlySelectedDeploymentArtifactDiv; + +/** + * Sets global variables currentlySelectedNodeTemplate and currentlySelectedDeploymentArtifactDiv + */ +function showDeploymentArtifactInformation(nodeTemplateId, deploymentArtifactName) { + currentlySelectedNodeTemplate = nodeTemplateId; + var daDiv = $("#" + nodeTemplateId).children("div.deploymentArtifactsContainer").children("div.content").children("div.deploymentArtifact").children("div.name:contains(" + deploymentArtifactName + ")").parent(); + currentlySelectedDeploymentArtifactDiv = daDiv; + var xml = daDiv.children("textarea").val(); + + // get values to display directly from the "UI" instead of parsing the XML and asking the server for appropriate names + var daArtifactTemplateName = daDiv.children("div.artifactTemplate").text(); + var daArtifactTypeName = daDiv.children("div.artifactType").text(); + + // determine URLs + require(["winery-support-common"], function(wsc) { + xmlDoc = wsc.getDocument(xml); + da = xmlDoc.firstChild; + + $("#DAname").text(deploymentArtifactName); + + $("#DAArtifactType").text(daArtifactTypeName); + addHref($("#DAArtifactType"), "artifacttypes", "artifactType", da); + + var at = $("#DAArtifactTemplate"); + if (daArtifactTemplateName != "") { + at.text(daArtifactTemplateName); + addHref(at, "artifacttemplates", "artifactRef", da); + } else { + at.text("No template associated"); + at.removeAttr("href"); + } + + $("#DAXML").val(xml); + + $("#DeploymentArtifactInfo").modal("show"); + }); +} + +/** + * Adds the given data to the deployment artifacts table of the currently active node template + * + * @param xmlAsDOM XML DOM document, TDeploymentArtifact. Produced by org.eclipse.winery.resources.artifacts.GenericArtifactsResource.onPost(String, String, String, String, String, String, String, String) + * @param xmlAsString + */ +function addDeploymentArtifact(xmlAsDOM, xmlAsString) { + var da = xmlAsDOM.firstChild; + var daName = da.getAttribute("name"); + + // we do NOT extract artifactType / artifactTemplate from the XML, but use the user input + // showDeploymentArtifactInformation will extract its data directly from the XML without querying some input at the other HTML elements + var daArtifactTemplateName = $("#artifactTemplateName").val(); + var daArtifactTypeName = $("#artifactType option:selected").text(); + + // add information to node template shape + var daData = { + nodeTemplateId : currentlySelectedNodeTemplate, + name : daName, + xml : xmlAsString, + artifactTypeName: daArtifactTypeName + }; + if (daArtifactTemplateName != "") { + daData.artifactTemplateName = daArtifactTemplateName; + } + addDeploymentArtifactInfoToNodeTemplate(daData); +} + +function addDeploymentArtifactInfoToNodeTemplate(daData) { + require(["tmpl"], function(tmpl){ + var data = tmpl("tmpl-deploymentArtifact", daData); + var element = $("#" + currentlySelectedNodeTemplate).children(".deploymentArtifactsContainer").children(".content").children(".addDA:first"); + element.before(data); + }); +} + +/** + * This function directly accesses the fields of the dialog, because the return value of the server is XML and we do not want to parse XML + * + * @param artifactInfo = {name, interfaceName (may be undefined), operationName (may be undefined), artifactTemplate (QName, may be undefined), artifactType} + */ +function artifactAddedSuccessfully(artifactInfo) { + var typeNsAndId = getNamespaceAndLocalNameFromQName(artifactInfo.artifactType); + var artifactTemplateNSAndId; + if (artifactInfo.artifactTemplate) { + artifactTemplateNSAndId = getNamespaceAndLocalNameFromQName(artifactInfo.artifactTemplate); + } else { + artifactTemplateNSAndId = undefined; + } + + var daData = { + nodeTemplateId : currentlySelectedNodeTemplate, + name : artifactInfo.name, + artifactTypeName: typeNsAndId.localname, + artifactTypeNSAndId: typeNsAndId, + artifactTemplateName: artifactInfo.artifactTemplateName, + artifactTemplateNSAndId: artifactTemplateNSAndId + }; + require(["tmpl"], function(tmpl){ + daData.xml = tmpl("tmpl-deploymentArtifactXML", daData); + addDeploymentArtifactInfoToNodeTemplate(daData); + }); +} + +// variables used for creation of deployment artifacts +var artifactTemplateAutoCreationEnabled = true; +var syncDAnameWithATname; + +// introduced by the handling of deployment and implementation artifacts +// holds the ID only (!) +var currentlySelectedNodeTemplate; + +/** + * FIXME: this function is not updated to the the new dialog design and not included any more + * + * It should be used if the checkbox for at creation changes its checked status or if the at name is not valid + * + */ +function updateArtifactTemplateCreationEnablement(value) { + // remove field highlights + // (currently, no intelligent removal and addition is made) + $("#artifactName").removeClass("highlight"); + $("#artifactTemplateName").removeClass("highlight"); + + if (value) { + // enable it + artifactTemplateAutoCreationEnabled = true; + $("#artifactTemplateName").removeAttr("disabled"); + $("#artifactTemplateNS").removeAttr("disabled"); + $("#createWithoutFilesBtn").attr("disabled", "disabled"); + $("#createWithFilesBtn").removeAttr("disabled"); + } else { + // disable it + artifactTemplateAutoCreationEnabled = false; + $("#artifactTemplateName").attr("disabled", "disabled"); + $("#artifactTemplateNS").attr("disabled", "disabled"); + $("#createWithoutFilesBtn").removeAttr("disabled"); + $("#createWithFilesBtn").attr("disabled", "disabled"); + } +} + +function isShownNodeTemplateShapeChangeBoxes(shape) { + return (shape.find(".endpointContainer").is(":visible")); +} + +/** + * @param shape jQuery object + */ +function showNodeTemplateShapeChangeBoxes(shape) { + shape.find(".addDA").show(); + shape.children(".endpointContainer").show(); + shape.find(".addnewreqorcap").show(); + shape.find(".addnewpolicy").show(); +} + +/** + * @param shape jQuery object + */ +function hideNodeTemplateShapeChangeBoxes(shape) { + shape.find(".addDA").hide(); + shape.children(".endpointContainer").hide(); + shape.find(".addnewreqorcap").hide(); + shape.find(".addnewpolicy").hide(); +} + +// indicates if a connection is currently drawn +// used to decide whether the node template boxes should be displayed +var isInConnectionMode = false; + +function wineryMoveSelectedNodeTemplateShapes(dX, dY) { + var shapes = $("div.NodeTemplateShape.selected"); + hideNodeTemplateShapeChangeBoxes(shapes); + shapes.each(function(i, nodeTemplate) { + nodeTemplate = $(nodeTemplate); + var offset = nodeTemplate.offset(); + offset.left += dX; + offset.top += dY; + nodeTemplate.offset(offset); + }); + jsPlumb.repaint(shapes); +} + + +/** + * Simple eventing framework + * + * use + * winery.events.register(name, function) to register on an event + * and + * winery.events.fire(name) to fire all registered functions + */ + +winery = {}; +winery.events = { + _events : {}, + + /** + * Registers a function + * + * @return the registered function + */ + register : function(eventName, f) { + if (!winery.events._events[eventName]) { + winery.events._events[eventName] = {}; + } + winery.events._events[eventName][f] = f; + return f; + }, + + /** + * Fires all functions associated with the given event name + */ + fire : function(eventName) { + if (winery.events._events[eventName]) { + $.each(winery.events._events[eventName], function(index, value) { + value(); + }); + } + return true; + } +}; + +/** + * Determines whether a key combo is allowed. + * + * For instance, when a modal dialog is opened or a input is selected, DEL should not delete node template shapes + */ +function keyComboAllowed() { + return ((!$(document.activeElement).is("input")) && ($("div.modal:visible").size() == 0)); +} + +function keyComboAllowedAndNodeTemplatesSelected() { + return (keyComboAllowed() && ($("div.NodeTemplateShape.selected").size() != 0)); +} + + + + +/* list of event names */ +winery.events.name = {}; +winery.events.name.command = {}; + +winery.events.name.SELECTION_CHANGED = "selectionchanged"; +winery.events.name.command.SELECT_ALL_NODETEMPLATES = "selectAllNodeTemplates"; +winery.events.name.command.UNSELECT_ALL_NODETEMPLATES = "unselectAllNodeTemplates"; +winery.events.name.command.DELETE_SELECTION = "deleteSelection"; + +winery.events.name.command.MOVE_DOWN = "moveDown"; +winery.events.name.command.MOVE_UP = "moveUp"; +winery.events.name.command.MOVE_LEFT = "moveLeft"; +winery.events.name.command.MOVE_RIGHT = "moveRight"; + +winery.events.name.command.SAVE = "save"; + diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/shared/README.md b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/shared/README.md new file mode 100644 index 0000000..4cb255e --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/shared/README.md @@ -0,0 +1 @@ +This folder is shared between repository and topology modeler
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/shared/dialogs.jsp b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/shared/dialogs.jsp new file mode 100644 index 0000000..21a7fec --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/shared/dialogs.jsp @@ -0,0 +1,83 @@ +<%-- +/******************************************************************************* + * Copyright (c) 2012-2013 University of Stuttgart. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Oliver Kopp - initial API and implementation and/or initial documentation + * Yves Schubert - switch to bootstrap 3 + *******************************************************************************/ +--%> +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> + +<script> +/** + * Displays a nice message box with "yes" and "no" + * + * TODO: currently cannot be recursively called + * + * @param msg Message to display + * @param fnOnYes function to be called if user presses "yes" + * @param title (optional) title of the dialog + */ +function vConfirmYesNo(msg, fnOnYes, title) { + title = title || "Please confirm"; + $("#diagyesnotitle").text(title); + $("#diagyesnomsg").text(msg); + $("#diagyesnoyesbtn").off("click"); + $("#diagyesnoyesbtn").on("click", function() { + var diag = $("#diagyesno"); + // quick hack to get fnOnYes() working -> use the hidden.bs.modal event + diag.on("hidden.bs.modal", function() { + fnOnYes(); + diag.off("hidden.bs.modal"); + }); + diag.modal("hide"); + }); + $("#diagyesno").modal("show"); +} + +$(function() { + $("#diagyesno").on("shown.bs.modal", function() { + $("#diagyesnoyesbtn").focus(); + }); +}); +</script> + +<div class="modal fade z1051" id="diagyesno"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title" id="diagyesnotitle"></h4> + </div> + <div class="modal-body"> + <p id="diagyesnomsg"></p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">No</button> + <button id="diagyesnoyesbtn" type="button" class="btn btn-primary">Yes</button> + </div> + </div> + </div> +</div> + +<div class="modal fade z1060" id="diagmessage"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title" id="diagmessagetitle"></h4> + </div> + <div class="modal-body" id="diagmessagemsg"> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" data-dismiss="modal">OK</button> + </div> + </div> + </div> +</div> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/selectionHandler.jsp b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/selectionHandler.jsp new file mode 100644 index 0000000..81758a4 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/selectionHandler.jsp @@ -0,0 +1,25 @@ +<% +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +/** + * This JSP file adds Node and RelationshipTemplates to a topology XML String using the JAXBHelper class. + * After the java method has finished, the completed topology XML String is returned. + */ +%> +<%@page import="org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.JAXBHelper"%> + +<% + String topologyXML = JAXBHelper.addTemplatesToTopology(request.getParameter("topology"), request.getParameter("allChoices"), request.getParameter("selectedNodeTemplates"), request.getParameter("selectedRelationshipTemplates")); +%> + +<%=topologyXML%>
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/topologyCompletion.jsp b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/topologyCompletion.jsp new file mode 100644 index 0000000..6e7f006 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/topologyCompletion.jsp @@ -0,0 +1,175 @@ +<% +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +/** + * This JSP calls the topology completion and handles the response. + * It is called the event handler when "Complete Topology" is selected in the EnterCompletionInformationDiag. + * There are several possible responses from the completion: + * - the topology is complete: display a success message + * - the topology is complete, several solutions exist: display dialog to choose topology solution + * - topology completion interrupted: the user has to chose inserted Node or Relationship Templates + */ +%> + +<%@page import="java.io.StringWriter"%> +<%@page import="java.util.List"%> +<%@page import="java.util.Map"%> +<%@page import="javax.xml.bind.Marshaller"%> +<%@page import="javax.xml.bind.JAXBContext"%> +<%@page import="javax.xml.bind.JAXBException"%> +<%@page import="org.eclipse.winery.model.tosca.Definitions"%> +<%@page import="org.eclipse.winery.model.tosca.TEntityTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TNodeTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TServiceTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TTopologyTemplate"%> +<%@page import="org.eclipse.winery.topologymodeler.addons.topologycompleter.topologycompletion.CompletionInterface"%> + +<%@taglib prefix="tc" tagdir="/WEB-INF/tags/common/topologycompletion"%> + +<% + // parse Strings from the request to Boolean values + boolean stepByStep = Boolean.parseBoolean(request.getParameter("stepByStep")); + boolean restarted = Boolean.parseBoolean(request.getParameter("restarted")); + boolean overwriteTopology = Boolean.parseBoolean(request.getParameter("overwriteTopology")); + + // call the topology completion component which will return a message if it was successful. + CompletionInterface completionInterface = new CompletionInterface(); + String message = completionInterface.complete(request.getParameter("topology"), request.getParameter("stName"), request.getParameter("templateURL"), overwriteTopology, request.getParameter("topologyName"), request.getParameter("topologyNamespace"), request.getParameter("repositoryURL"), stepByStep, restarted); + + if (message.equals("success")) { %> + <script> vShowSuccess('Completion Successful!'); </script> + <% + } else if (message.equals("topologyComplete") && !restarted) { %> + <script> + vShowSuccess('The topology is already complete.'); + </script> + <% + } else if (message.equals("failure")) { + %> + <p> <%=completionInterface.getErrorMessage()%> </p> + <%} else if (message.equals("userInteraction")) { + + // a user interaction is necessary to choose RelationshipTemplates, receive + // the current topology and the choices from the CompletionInterface + // and display them via relationshipTemplateSelector.jsp + + TTopologyTemplate currentTopology = completionInterface.getCurrentTopology(); + List<TEntityTemplate> relationshipTemplateSelection = completionInterface.getRelationshipTemplateChoices(); + + ///////////////////////////////////////////////////// + // Convert JAXB objects of the topology and the + // Relationship Templates to be chosen to XML Strings + ///////////////////////////////////////////////////// + + Definitions definitions = new Definitions(); + TServiceTemplate serviceTemplate = new TServiceTemplate(); + + serviceTemplate.setTopologyTemplate(currentTopology); + definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().add(serviceTemplate); + JAXBContext context = JAXBContext.newInstance(Definitions.class); + Marshaller marshaller = context.createMarshaller(); + StringWriter currentTopologyString = new StringWriter(); + + marshaller.marshal(definitions, currentTopologyString); + + TTopologyTemplate topologyTemplate = new TTopologyTemplate(); + + // add all choices to a TopologyTemplate + for (TEntityTemplate entityTemplate: relationshipTemplateSelection) { + topologyTemplate.getNodeTemplateOrRelationshipTemplate().add(entityTemplate); + } + + // get the choices as XML + definitions = new Definitions(); + serviceTemplate = new TServiceTemplate(); + serviceTemplate.setTopologyTemplate(topologyTemplate); + definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().add(serviceTemplate); + context = JAXBContext.newInstance(Definitions.class); + StringWriter choicesAsXMLString = new StringWriter(); + + marshaller.marshal(definitions, choicesAsXMLString); + %> + <script> + var topology = "<%=currentTopologyString.toString()%>"; + var choices = "<%=choicesAsXMLString.toString()%>"; + </script> + <!-- call the relationshipTemplateSelector tag to render the selection to graphic objects --> + <tc:relationshipTemplateSelector choices='<%=relationshipTemplateSelection%>' templateURL='<%=request.getParameter("templateURL")%>' topologyName='<%=request.getParameter("topologyName")%>' + topologyNamespace='<%=request.getParameter("topologyNamespace")%>' repositoryURL='<%=request.getParameter("repositoryURL")%>' stName='<%=request.getParameter("stName")%>' /> + <%} else if (message.equals("userTopologySelection")) { + // there are several topology solutions. Receive the choices from the CompletionInterface + // and display them via topologyTemplateSelector.tag + List<TTopologyTemplate> topologyTemplateSelection = completionInterface.getTopologyTemplateChoices(); + %> + <!-- call the topologyTemplateSelector tag to render the selection to graphic objects --> + <tc:topologyTemplateSelector solutionTopologies='<%=topologyTemplateSelection%>' templateURL='<%=request.getParameter("templateURL")%>' topologyName='<%=request.getParameter("topologyName")%>' + topologyNamespace='<%=request.getParameter("topologyNamespace")%>' repositoryURL='<%=request.getParameter("repositoryURL")%>' /> + <% + } else if (message.equals("stepByStep")) { + + // the topology completion is processed step-by-step. The user has to choose inserted Node and RelationshipTemplates + TTopologyTemplate currentTopology = completionInterface.getCurrentTopology(); + Map<TNodeTemplate, Map<TNodeTemplate, List<TEntityTemplate>>> nodeTemplateSelection = completionInterface.getNodeTemplateChoices(); + + /////////////////////////////////////////////// + // Convert JAXB objects of the topology and the + // Templates to be chosen to XML Strings + /////////////////////////////////////////////// + + Definitions definitions = new Definitions(); + TServiceTemplate serviceTemplate = new TServiceTemplate(); + serviceTemplate.setTopologyTemplate(currentTopology); + definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().add(serviceTemplate); + JAXBContext context = JAXBContext.newInstance(Definitions.class); + Marshaller marshaller = context.createMarshaller(); + StringWriter currentTopologyString = new StringWriter(); + + marshaller.marshal(definitions, currentTopologyString); + + // add all choices to a TopologyTemplate + TTopologyTemplate topologyTemplate = new TTopologyTemplate(); + + for (TNodeTemplate nodeTemplate: nodeTemplateSelection.keySet()) { + Map<TNodeTemplate, List<TEntityTemplate>> entityTemplates = nodeTemplateSelection.get(nodeTemplate); + + for (TNodeTemplate entity: entityTemplates.keySet()) { + topologyTemplate.getNodeTemplateOrRelationshipTemplate().add(entity); + topologyTemplate.getNodeTemplateOrRelationshipTemplate().addAll(entityTemplates.get(entity)); + } + topologyTemplate.getNodeTemplateOrRelationshipTemplate().add(nodeTemplate); + } + + // get the choices as XML + definitions = new Definitions(); + serviceTemplate = new TServiceTemplate(); + serviceTemplate.setTopologyTemplate(topologyTemplate); + definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().add(serviceTemplate); + + StringWriter choicesAsXMLString = new StringWriter(); + + marshaller.marshal(definitions, choicesAsXMLString); + + %> + <script> + var topology = '<%=currentTopologyString.toString()%>'; + var choices = '<%=choicesAsXMLString.toString()%>'; + </script> + <!-- call the tc:nodeTemplateSelector tag to render the selection to graphic objects --> + <tc:nodeTemplateSelector choices='<%=nodeTemplateSelection%>' templateURL='<%=request.getParameter("templateURL")%>' topologyName='<%=request.getParameter("topologyName")%>' + topologyNamespace='<%=request.getParameter("topologyNamespace")%>' repositoryURL='<%=request.getParameter("repositoryURL")%>' stName='<%=request.getParameter("stName")%>' /> + <%} +%> + +<script> + var message = "<%=message%>"; +</script> diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/topologySaver.jsp b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/topologySaver.jsp new file mode 100644 index 0000000..671baee --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/jsp/topologyCompletion/topologySaver.jsp @@ -0,0 +1,50 @@ +<% +/******************************************************************************* + * Copyright (c) 2013 Pascal Hirmer. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and the Apache License 2.0 which both accompany this distribution, + * and are available at http://www.eclipse.org/legal/epl-v10.html + * and http://www.apache.org/licenses/LICENSE-2.0 + * + * Contributors: + * Pascal Hirmer - initial API and implementation + *******************************************************************************/ + +/** + * This JSP saves a topology template to the repository using the RESTHelper class. + * It is called when there are several topology solutions which shall be saved in different locations. + */ +%> +<%@page import="org.eclipse.winery.topologymodeler.addons.topologycompleter.helper.RESTHelper"%> +<%@page import="java.io.StringReader"%> +<%@page import="javax.xml.bind.JAXBContext"%> +<%@page import="javax.xml.bind.JAXBException"%> +<%@page import="javax.xml.bind.Unmarshaller"%> +<%@page import="org.eclipse.winery.model.tosca.Definitions"%> +<%@page import="org.eclipse.winery.model.tosca.TServiceTemplate"%> +<%@page import="org.eclipse.winery.model.tosca.TTopologyTemplate"%> + +<% + String xmlString = request.getParameter("topology"); + String templateURL = request.getParameter("templateURL"); + String repositoryURL = request.getParameter("repositoryURL"); + + // initiate JaxB context + JAXBContext context; + context = JAXBContext.newInstance(Definitions.class); + StringReader reader = new StringReader(xmlString); + + // unmarshall the topology XML string + Unmarshaller um = context.createUnmarshaller(); + Definitions jaxBDefinitions = (Definitions) um.unmarshal(reader); + TServiceTemplate st = (TServiceTemplate) jaxBDefinitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().get(0); + TTopologyTemplate toBeSaved = st.getTopologyTemplate(); + + // depending on the selected save method (overwrite or create new) the save method is called + if (request.getParameter("overwriteTopology").equals("true")) { + RESTHelper.saveCompleteTopology(toBeSaved, templateURL, true, "", "", repositoryURL); + } else { + RESTHelper.saveCompleteTopology(toBeSaved, templateURL, false, request.getParameter("topologyName"), request.getParameter("topologyNamespace"), repositoryURL); + } +%>
\ No newline at end of file diff --git a/winery/org.eclipse.winery.topologymodeler/src/main/webapp/logback.xml b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/logback.xml new file mode 100644 index 0000000..6602380 --- /dev/null +++ b/winery/org.eclipse.winery.topologymodeler/src/main/webapp/logback.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%line %method - %msg%n</pattern> + </encoder> + </appender> + + <logger name="org.eclipse.winery" level="DEBUG"/> + + <root level="info"> + <appender-ref ref="STDOUT" /> + </root> +</configuration> |