Author: blabno
Date: 2011-09-23 04:44:46 -0400 (Fri, 23 Sep 2011)
New Revision: 22734
Added:
sandbox/trunk/ui/visualsearch/
sandbox/trunk/ui/visualsearch/bom/
sandbox/trunk/ui/visualsearch/bom/pom.xml
sandbox/trunk/ui/visualsearch/demo/
sandbox/trunk/ui/visualsearch/demo/pom.xml
sandbox/trunk/ui/visualsearch/demo/src/
sandbox/trunk/ui/visualsearch/demo/src/main/
sandbox/trunk/ui/visualsearch/demo/src/main/java/
sandbox/trunk/ui/visualsearch/demo/src/main/java/org/
sandbox/trunk/ui/visualsearch/demo/src/main/java/org/richfaces/
sandbox/trunk/ui/visualsearch/demo/src/main/java/org/richfaces/sandbox/
sandbox/trunk/ui/visualsearch/demo/src/main/java/org/richfaces/sandbox/visualsearch/
sandbox/trunk/ui/visualsearch/demo/src/main/java/org/richfaces/sandbox/visualsearch/Bean.java
sandbox/trunk/ui/visualsearch/demo/src/main/resources/
sandbox/trunk/ui/visualsearch/demo/src/main/resources/rebel.xml
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/MANIFEST.MF
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/context.xml
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/faces-config.xml
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/web.xml
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/index.jsp
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/menu.xhtml
sandbox/trunk/ui/visualsearch/demo/src/main/webapp/sample_1.xhtml
sandbox/trunk/ui/visualsearch/parent/
sandbox/trunk/ui/visualsearch/parent/pom.xml
sandbox/trunk/ui/visualsearch/pom.xml
sandbox/trunk/ui/visualsearch/ui/
sandbox/trunk/ui/visualsearch/ui/pom.xml
sandbox/trunk/ui/visualsearch/ui/src/
sandbox/trunk/ui/visualsearch/ui/src/main/
sandbox/trunk/ui/visualsearch/ui/src/main/java/
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/AbstractVisualsearch.java
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/FacetSuggestionEvent.java
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/FacetSuggestionListener.java
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/SearchEvent.java
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/SearchListener.java
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/ValueSuggestionEvent.java
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/ValueSuggestionListener.java
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/package-info.java
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/renderkit/
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/renderkit/html/
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/renderkit/html/VisualsearchRenderer.java
sandbox/trunk/ui/visualsearch/ui/src/main/resources/
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/backbone.js
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.autocomplete.js
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.core.js
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.position.js
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.widget.js
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/richfaces.visualsearch.js
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/underscore.js
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/visualsearch-datauri.css
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/visualsearch.js
sandbox/trunk/ui/visualsearch/ui/src/test/
sandbox/trunk/ui/visualsearch/ui/src/test/java/
Log:
Initial import of visualsearch component
Added: sandbox/trunk/ui/visualsearch/bom/pom.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/bom/pom.xml (rev 0)
+++ sandbox/trunk/ui/visualsearch/bom/pom.xml 2011-09-23 08:44:46 UTC (rev 22734)
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+
+<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.richfaces.sandbox.ui.visualsearch</groupId>
+ <artifactId>visualsearch-bom</artifactId>
+ <version>4.1.0-SNAPSHOT</version>
+ <name>Richfaces UI Components: visualsearch bom</name>
+ <packaging>pom</packaging>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.richfaces</groupId>
+ <artifactId>richfaces-bom</artifactId>
+ <version>${project.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>visualsearch-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ </dependencies>
+ </dependencyManagement>
+
+ <distributionManagement>
+ <snapshotRepository>
+ <id>bernard.labno.pl</id>
+ <name>MyCo Internal Repository</name>
+
<url>http://bernard.labno.pl/artifactory/libs-snapshot-local</url>
+ </snapshotRepository>
+ </distributionManagement>
+
+</project>
Added: sandbox/trunk/ui/visualsearch/demo/pom.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/pom.xml (rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/pom.xml 2011-09-23 08:44:46 UTC (rev 22734)
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.richfaces.sandbox.ui.visualsearch</groupId>
+ <artifactId>visualsearch-parent</artifactId>
+ <version>4.1.0-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>visualsearch-demo</artifactId>
+ <name>Richfaces UI Components: visualsearch demo</name>
+ <packaging>war</packaging>
+ <build>
+ <finalName>visualsearch-demo</finalName>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.richfaces.ui</groupId>
+ <artifactId>richfaces-components-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.richfaces.core</groupId>
+ <artifactId>richfaces-core-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.richfaces.sandbox.ui.visualsearch</groupId>
+ <artifactId>visualsearch-ui</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.faces</groupId>
+ <artifactId>jsf-api</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.faces</groupId>
+ <artifactId>jsf-impl</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.el</groupId>
+ <artifactId>el-ri</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+</project>
+
+
Added:
sandbox/trunk/ui/visualsearch/demo/src/main/java/org/richfaces/sandbox/visualsearch/Bean.java
===================================================================
---
sandbox/trunk/ui/visualsearch/demo/src/main/java/org/richfaces/sandbox/visualsearch/Bean.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/demo/src/main/java/org/richfaces/sandbox/visualsearch/Bean.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,138 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright , Red Hat, Inc. and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.richfaces.sandbox.visualsearch;
+
+import org.richfaces.component.event.FacetSuggestionEvent;
+import org.richfaces.component.event.SearchEvent;
+import org.richfaces.component.event.ValueSuggestionEvent;
+
+import javax.faces.application.FacesMessage;
+import javax.faces.context.FacesContext;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Bean implements Serializable {
+// ------------------------------ FIELDS ------------------------------
+
+ private int counter;
+
+ private List<String> facets;
+
+ private String query;
+
+ private String value;
+
+ private Map<String, List<String>> values;
+
+// --------------------------- CONSTRUCTORS ---------------------------
+
+ public Bean()
+ {
+ facets = new ArrayList<String>();
+ facets.add("Status");
+ facets.add("Version");
+ facets.add("Assignee");
+
+ values = new HashMap<String, List<String>>();
+ List<String> list = new ArrayList<String>();
+ values.put("Status", list);
+ list.add("Open");
+ list.add("Closed");
+ list.add("In Progress");
+
+ list = new ArrayList<String>();
+ values.put("Version", list);
+ list.add("1.0.0");
+ list.add("1.0.1");
+ list.add("1.0.2");
+ list = new ArrayList<String>();
+ values.put("Assignee", list);
+ list.add("John");
+ list.add("Brian");
+ list.add("Scott");
+ }
+
+// --------------------- GETTER / SETTER METHODS ---------------------
+
+ public String getQuery()
+ {
+ return query;
+ }
+
+ public void setQuery(String query)
+ {
+ this.query = query;
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+
+ public void setValue(String value)
+ {
+ this.value = value;
+ }
+
+// -------------------------- OTHER METHODS --------------------------
+
+ public int getCounter()
+ {
+ return counter++;
+ }
+
+ public List<String> getFixedFacetSuggestions()
+ {
+ return facets;
+ }
+
+ public Map<String, List<String>> getFixedValueSuggestions()
+ {
+ return values;
+ }
+
+ public void search(SearchEvent event)
+ {
+ FacesContext.getCurrentInstance().addMessage(null, new
FacesMessage("Searching for: " + getValue()));
+ }
+
+ public List<String> suggestFacet(FacetSuggestionEvent event)
+ {
+ /**
+ * We could even filter this list by event.getSearchTerm()
+ */
+ return facets;
+ }
+
+ public List<String> suggestValue(ValueSuggestionEvent event)
+ {
+ final List<String> list = values.get(event.getFacet());
+ /**
+ * We could even filter this list by event.getSearchTerm()
+ */
+ return list;
+ }
+}
Added: sandbox/trunk/ui/visualsearch/demo/src/main/resources/rebel.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/src/main/resources/rebel.xml
(rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/src/main/resources/rebel.xml 2011-09-23 08:44:46
UTC (rev 22734)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<application
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.zeroturnaround.com"
+
xsi:schemaLocation="http://www.zeroturnaround.com
http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd">
+
+ <classpath>
+ <dir
name="/home/bernard/projects/richfaces/sandbox/trunk/ui/visualsearch/demo/target/classes"></dir>
+ <dir
name="/home/bernard/projects/richfaces/sandbox/trunk/ui/visualsearch/ui/target/classes"></dir>
+ </classpath>
+
+ <web>
+ <link target="/">
+ <dir
name="/home/bernard/projects/richfaces/sandbox/trunk/ui/visualsearch/demo/src/main/webapp"></dir>
+ </link>
+ </web>
+
+</application>
Added: sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/MANIFEST.MF
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/MANIFEST.MF
(rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/MANIFEST.MF 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path:
+
Added: sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/context.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/context.xml
(rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/src/main/webapp/META-INF/context.xml 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+
+<Context antiJARLocking="true" path="/notify-demo"/>
Added: sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/faces-config.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/faces-config.xml
(rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/faces-config.xml 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,31 @@
+<?xml version='1.0' encoding='UTF-8'?><!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+
+<faces-config
xmlns="http://java.sun.com/xml/ns/javaee"
version="2.0">
+
+ <managed-bean>
+ <managed-bean-name>bean</managed-bean-name>
+
<managed-bean-class>org.richfaces.sandbox.visualsearch.Bean</managed-bean-class>
+ <managed-bean-scope>session</managed-bean-scope>
+ </managed-bean>
+
+</faces-config>
\ No newline at end of file
Added: sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/web.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/web.xml
(rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/src/main/webapp/WEB-INF/web.xml 2011-09-23 08:44:46
UTC (rev 22734)
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+
+<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
+
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID"
version="2.5">
+ <display-name>Schedule demo</display-name>
+ <context-param>
+ <param-name>javax.faces.CONFIG_FILES</param-name>
+ <param-value>/WEB-INF/faces-config.xml</param-value>
+ </context-param>
+ <context-param>
+ <param-name>org.richfaces.SKIN</param-name>
+ <param-value>classic</param-value>
+ </context-param>
+ <context-param>
+ <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
+ <param-value>.xhtml</param-value>
+ </context-param>
+ <context-param>
+ <param-name>javax.faces.FACELETS_REFRESH_PERIOD</param-name>
+ <param-value>2</param-value>
+ </context-param>
+ <context-param>
+ <param-name>facelets.DEVELOPMENT</param-name>
+ <param-value>true</param-value>
+ </context-param>
+ <context-param>
+ <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
+ <param-value>true</param-value>
+ </context-param>
+ <context-param>
+ <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
+ <param-value>server</param-value>
+ </context-param>
+ <context-param>
+ <param-name>com.sun.faces.validateXml</param-name>
+ <param-value>true</param-value>
+ </context-param>
+ <context-param>
+ <param-name>com.sun.faces.verifyObjects</param-name>
+ <param-value>false</param-value>
+ </context-param>
+ <context-param>
+ <param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
+ <param-value>com.sun.facelets.FaceletViewHandler</param-value>
+ </context-param>
+ <context-param>
+ <param-name>org.ajax4jsf.COMPRESS_SCRIPT</param-name>
+ <param-value>false</param-value>
+ </context-param>
+ <context-param>
+ <param-name>javax.faces.PROJECT_STAGE</param-name>
+ <param-value>Development</param-value>
+ </context-param>
+
+ <servlet>
+ <servlet-name>Faces Servlet</servlet-name>
+ <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>Faces Servlet</servlet-name>
+ <url-pattern>/faces/*</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>Faces Servlet</servlet-name>
+ <url-pattern>*.jsf</url-pattern>
+ </servlet-mapping>
+ <login-config>
+ <auth-method>BASIC</auth-method>
+ </login-config>
+</web-app>
Added: sandbox/trunk/ui/visualsearch/demo/src/main/webapp/index.jsp
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/src/main/webapp/index.jsp
(rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/src/main/webapp/index.jsp 2011-09-23 08:44:46 UTC
(rev 22734)
@@ -0,0 +1,29 @@
+<%--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ --%>
+
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head></head>
+<body>
+<jsp:forward page="sample_1.jsf"/>
+</body>
+</html>
Added: sandbox/trunk/ui/visualsearch/demo/src/main/webapp/menu.xhtml
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/src/main/webapp/menu.xhtml
(rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/src/main/webapp/menu.xhtml 2011-09-23 08:44:46 UTC
(rev 22734)
@@ -0,0 +1,28 @@
+<!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+
+<ui:component
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
+
xmlns:h="http://java.sun.com/jsf/html">
+ <h:panelGrid colsumns="3">
+ <h:outputLink
value="#{facesContext.externalContext.requestContextPath}/sample_1.jsf">Example
1</h:outputLink>
+ </h:panelGrid>
+</ui:component>
Added: sandbox/trunk/ui/visualsearch/demo/src/main/webapp/sample_1.xhtml
===================================================================
--- sandbox/trunk/ui/visualsearch/demo/src/main/webapp/sample_1.xhtml
(rev 0)
+++ sandbox/trunk/ui/visualsearch/demo/src/main/webapp/sample_1.xhtml 2011-09-23 08:44:46
UTC (rev 22734)
@@ -0,0 +1,109 @@
+<!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
+
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
+
xmlns:visualsearch="http://richfaces.org/sandbox/visualsearch">
+<h:head>
+ <title>Visualsearch sample</title>
+</h:head>
+<h:body class="rich-container">
+
+ <ui:include src="menu.xhtml"/>
+
+ <p>
+
+ Try typing Status, Version or Assignee :)
+
+ </p>
+
+ <rich:notifyMessages/>
+
+ <h:form>
+ Ajax mode:
+ <visualsearch:visualsearch query="#{bean.query}"
facetSuggestionListener="#{bean.suggestFacet}"
valueSuggestionListener="#{bean.suggestValue}"
+
onsearch="jQuery('#onsearch').html(query+' '+queryJSON)"
searchListener="#{bean.search}" value="#{bean.value}"
+ render="counter"/>
+ <br/>
+ <br/>
+ <br/>
+ <br/>
+ Client mode:
+ <visualsearch:visualsearch
onsearch="RichFaces.ui.Notify({severity:0,summary:query} )"
switchType="client"
+
valueSuggestions="#{bean.fixedValueSuggestions}"
facetSuggestions="#{bean.fixedFacetSuggestions}"/>
+ </h:form>
+
+ <a4j:outputPanel ajaxRendered="true">
+ <h:panelGrid columns="2">
+ bean.query:
+ <h:outputText value="#{bean.query}"/>
+ bean.value:
+ <h:outputText value="#{bean.value}"/>
+ </h:panelGrid>
+ </a4j:outputPanel>
+
+ <h:outputText id="counter" value="Counter:
#{bean.counter}"/>
+
+ <ol>
+ Questions & TODO:
+ <li>
+ Limit ajax request, the component is crazy with them!
+ </li>
+ <li>
+ Should this component be value holder?
+ Since it is also action source then when shall the value be updated, only on
search?
+ </li>
+ <li>
+ I think that server mode lacks sense with autocomplete.
+ </li>
+ <li>
+ Should we pass only query to onsearch listener or also stringified queryJSON
or maybe something else?
+ </li>
+ <li>
+ It would be cool to not re-render anything during suggestion and only on
search.
+ </li>
+ <li>
+ Actually I think that there should be only javascript onsearch hook that
someone could attach a4j:ajax to in stead of having one
+ built-in ajax function for all 3 hooks (facetSuggestion, valueSuggestion,
onsearch), what do you think?
+ <br/>
+ <br/>
+ Note that if we specify "render" attribute then it will be called
on every hook, like facetSuggestion, valueSuggestion, onsearch.
+ And I don't think anyone would like to refresh datatable with search
results until the entire search query is ready.
+ See the "Counter", how it increments on every request from
visualsearch component.
+ </li>
+ <li>
+ Should method bound to valueSuggestionListener accept ValueSuggestionEvent
param (which makes bean dependant on RF, thus view layer)
+ or should we pass string params. Problem with string params is that there can
be many of them (searchTerm,facet,query,queryJSON).
+ </li>
+ <li>
+ Right now valueSuggestions must return map of string facet name as key and
list of string values for that facet.
+ Maybe it would be cool to allow attaching converter for suggestions, thus
allowing them to be list of objects.
+ Or even better add extra tag like
<visualsearch:visualsearchValueSuggestion facet="#{obj.someName}"
value="#{obj.someValue}"/>
+ and visualsearch:visualsearchFacetSuggestion.
+ </li>
+ </ol>
+
+ <h:messages/>
+</h:body>
+</html>
Added: sandbox/trunk/ui/visualsearch/parent/pom.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/parent/pom.xml (rev 0)
+++ sandbox/trunk/ui/visualsearch/parent/pom.xml 2011-09-23 08:44:46 UTC (rev 22734)
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+
+<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.richfaces</groupId>
+ <artifactId>richfaces-root-parent</artifactId>
+ <version>4.1.0-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.richfaces.sandbox.ui.visualsearch</groupId>
+ <artifactId>visualsearch-parent</artifactId>
+ <name>Richfaces UI Components: visualsearch parent</name>
+ <packaging>pom</packaging>
+
+ <properties>
+ <richfaces.checkstyle.version>1</richfaces.checkstyle.version>
+
<org.richfaces.cdk.version>4.1.0-SNAPSHOT</org.richfaces.cdk.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.richfaces.sandbox.ui.visualsearch</groupId>
+ <artifactId>visualsearch-bom</artifactId>
+ <version>${project.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>org.richfaces.cdk</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${org.richfaces.cdk.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.richfaces.cdk</groupId>
+ <artifactId>maven-cdk-plugin</artifactId>
+ <version>${org.richfaces.cdk.version}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.1</version>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <version>1.0-beta-2</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-archetype-plugin</artifactId>
+ <version>2.0-alpha-4</version>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <version>1.0-beta-1</version>
+ <configuration>
+ <fail>false</fail>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <distributionManagement>
+ <snapshotRepository>
+ <id>bernard.labno.pl</id>
+ <name>MyCo Internal Repository</name>
+
<url>http://bernard.labno.pl/artifactory/libs-snapshot-local</url>
+ </snapshotRepository>
+ </distributionManagement>
+
+</project>
Added: sandbox/trunk/ui/visualsearch/pom.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/pom.xml (rev 0)
+++ sandbox/trunk/ui/visualsearch/pom.xml 2011-09-23 08:44:46 UTC (rev 22734)
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.richfaces.sandbox.ui.visualsearch</groupId>
+ <artifactId>visualsearch-parent</artifactId>
+ <version>4.1.0-SNAPSHOT</version>
+ <relativePath>parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>visualsearch-aggregator</artifactId>
+ <packaging>pom</packaging>
+ <name>Richfaces UI Components: visualsearch Aggregator</name>
+
+ <modules>
+ <module>bom</module>
+ <module>parent</module>
+ <module>ui</module>
+ <module>demo</module>
+ </modules>
+
+ <profiles>
+ <profile>
+ <id>cli</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.twdata.maven</groupId>
+ <artifactId>maven-cli-plugin</artifactId>
+ <version>1.0.6-SNAPSHOT</version>
+ <configuration>
+ <userAliases>
+ <ui>visualsearch-ui clean install</ui>
+ <demo>visualsearch-demo clean package</demo>
+ </userAliases>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <distributionManagement>
+ <snapshotRepository>
+ <id>bernard.labno.pl</id>
+ <name>MyCo Internal Repository</name>
+
<url>http://bernard.labno.pl/artifactory/libs-snapshot-local</url>
+ </snapshotRepository>
+ </distributionManagement>
+
+</project>
Added: sandbox/trunk/ui/visualsearch/ui/pom.xml
===================================================================
--- sandbox/trunk/ui/visualsearch/ui/pom.xml (rev 0)
+++ sandbox/trunk/ui/visualsearch/ui/pom.xml 2011-09-23 08:44:46 UTC (rev 22734)
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ JBoss, Home of Professional Open Source
+ Copyright , Red Hat, Inc. and individual contributors
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ -->
+
+<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.richfaces.sandbox.ui.visualsearch</groupId>
+ <artifactId>visualsearch-parent</artifactId>
+ <version>4.1.0-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>visualsearch-ui</artifactId>
+ <name>Richfaces UI Components: visualsearch ui</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.richfaces.ui.core</groupId>
+ <artifactId>richfaces-ui-core-ui</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.richfaces.ui</groupId>
+ <artifactId>richfaces-components-ui</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.richfaces.cdk</groupId>
+ <artifactId>annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.el</groupId>
+ <artifactId>el-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.test-jsf</groupId>
+ <artifactId>jsf-test-stage</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.richfaces.cdk</groupId>
+ <artifactId>maven-cdk-plugin</artifactId>
+ <version>${org.richfaces.cdk.version}</version>
+ <executions>
+ <execution>
+ <id>cdk-generate-sources</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <scm>
+
<
connection>scm:svn:http://anonsvn.jboss.org/repos/richfaces/sandbox/tr...
+
<
developerConnection>scm:svn:https://svn.jboss.org/repos/richfaces/sand...
+ <
url>http://fisheye.jboss.org/browse/richfaces/</url>
+ </scm>
+
+</project>
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/AbstractVisualsearch.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/AbstractVisualsearch.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/AbstractVisualsearch.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,137 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright , Red Hat, Inc. and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.richfaces.component;
+
+import org.richfaces.cdk.annotations.Attribute;
+import org.richfaces.cdk.annotations.Event;
+import org.richfaces.cdk.annotations.EventName;
+import org.richfaces.cdk.annotations.JsfComponent;
+import org.richfaces.cdk.annotations.JsfRenderer;
+import org.richfaces.cdk.annotations.Signature;
+import org.richfaces.cdk.annotations.Tag;
+import org.richfaces.cdk.annotations.TagType;
+import org.richfaces.component.event.FacetSuggestionEvent;
+import org.richfaces.component.event.FacetSuggestionListener;
+import org.richfaces.component.event.SearchEvent;
+import org.richfaces.component.event.ValueSuggestionEvent;
+import org.richfaces.component.event.ValueSuggestionListener;
+import org.richfaces.context.ExtendedPartialViewContext;
+import org.richfaces.renderkit.html.VisualsearchRenderer;
+
+import javax.el.MethodExpression;
+import javax.faces.component.UIInput;
+import javax.faces.context.FacesContext;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.FacesEvent;
+import java.util.List;
+import java.util.Map;
+
+@JsfComponent(tag = @Tag(name = "visualsearch", handler =
"org.richfaces.view.facelets.html.VisualsearchTagHandler", generate = true, type
= TagType.Facelets),
+ fires = {@Event(value = FacetSuggestionEvent.class, listener =
FacetSuggestionListener.class),
+ @Event(value = ValueSuggestionEvent.class, listener =
ValueSuggestionListener.class)},
+ renderer = @JsfRenderer(family = AbstractVisualsearch.COMPONENT_FAMILY, type =
VisualsearchRenderer.RENDERER_TYPE),
+ attributes = {"ajax-props.xml"})
+public abstract class AbstractVisualsearch extends UIInput {
+// ------------------------------ FIELDS ------------------------------
+
+ public static final String COMPONENT_FAMILY =
"org.richfaces.Visualsearch";
+
+ public static final String COMPONENT_TYPE = "org.richfaces.Visualsearch";
+
+ public static final String SWITCH_TYPE_AJAX = "ajax";
+
+ public static final String SWITCH_TYPE_CLIENT = "client";
+
+ public static final String _DEFAULT_SWITCH_TYPE = SWITCH_TYPE_AJAX;
+
+// -------------------------- OTHER METHODS --------------------------
+
+ @Override
+ public void broadcast(FacesEvent event) throws AbortProcessingException
+ {
+ if (event instanceof FacetSuggestionEvent) {
+ super.broadcast(event);
+ FacesContext facesContext = getFacesContext();
+ MethodExpression expression = getFacetSuggestionListener();
+ if (expression != null) {
+ setResponseData(expression.invoke(facesContext.getELContext(), new
Object[]{event}));
+ }
+ } else if (event instanceof ValueSuggestionEvent) {
+ super.broadcast(event);
+ FacesContext facesContext = getFacesContext();
+ MethodExpression expression = getValueSuggestionListener();
+ if (expression != null) {
+ setResponseData(expression.invoke(facesContext.getELContext(), new
Object[]{event}));
+ }
+ } else if (event instanceof SearchEvent) {
+ super.broadcast(event);
+ FacesContext facesContext = getFacesContext();
+ MethodExpression expression = getSearchListener();
+ if (expression != null) {
+ expression.invoke(facesContext.getELContext(), new Object[]{event});
+ }
+ } else {
+ super.broadcast(event);
+ }
+ }
+
+ @Attribute(signature = @Signature(parameters = FacetSuggestionEvent.class))
+ public abstract MethodExpression getFacetSuggestionListener();
+
+ @Attribute(events = @EventName(value = "search", defaultEvent = true))
+ public abstract String getOnsearch();
+
+ @Attribute
+ public abstract String getQuery();
+
+ @Attribute(signature = @Signature(parameters = SearchEvent.class))
+ public abstract MethodExpression getSearchListener();
+
+ @Attribute(defaultValue = "SwitchType." + _DEFAULT_SWITCH_TYPE,
+ suggestedValue = SWITCH_TYPE_AJAX + "," + SWITCH_TYPE_CLIENT)
+ public abstract SwitchType getSwitchType();
+
+ /**
+ * Use this to pass predefined facet suggestions (FacetSuggestionListener will not be
used).
+ *
+ * @return list of facet suggestions
+ */
+ @Attribute
+ public abstract List<String> getFacetSuggestions();
+
+ /**
+ * Use this to pass predefined value suggestions (ValueSuggestionListener will not be
used).
+ *
+ * @return map of value suggestions where key is facet name and value is list of
values for that facet
+ */
+ @Attribute
+ public abstract Map<String, List<String>> getValueSuggestions();
+
+ @Attribute(signature = @Signature(parameters = ValueSuggestionEvent.class))
+ public abstract MethodExpression getValueSuggestionListener();
+
+ private void setResponseData(Object data)
+ {
+
ExtendedPartialViewContext.getInstance(getFacesContext()).getResponseComponentDataMap().put(getClientId(getFacesContext()),
data);
+ }
+}
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/FacetSuggestionEvent.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/FacetSuggestionEvent.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/FacetSuggestionEvent.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,46 @@
+package org.richfaces.component.event;
+
+import javax.faces.component.UIComponent;
+import javax.faces.event.FacesEvent;
+import javax.faces.event.FacesListener;
+
+public class FacetSuggestionEvent extends FacesEvent {
+// ------------------------------ FIELDS ------------------------------
+
+ private String searchTerm;
+
+// --------------------------- CONSTRUCTORS ---------------------------
+
+ public FacetSuggestionEvent(UIComponent component, String searchTerm)
+ {
+ super(component);
+ this.searchTerm = searchTerm;
+ }
+
+// --------------------- GETTER / SETTER METHODS ---------------------
+
+ public String getSearchTerm()
+ {
+ return searchTerm;
+ }
+
+// ------------------------ CANONICAL METHODS ------------------------
+
+ @Override
+ public String toString()
+ {
+ return getClass().getSimpleName() + "[text=" + searchTerm +
"]";
+ }
+
+// -------------------------- OTHER METHODS --------------------------
+
+ public boolean isAppropriateListener(FacesListener facesListener)
+ {
+ return facesListener instanceof FacetSuggestionListener;
+ }
+
+ public void processListener(FacesListener facesListener)
+ {
+ ((FacetSuggestionListener) facesListener).suggest(this);
+ }
+}
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/FacetSuggestionListener.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/FacetSuggestionListener.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/FacetSuggestionListener.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,9 @@
+package org.richfaces.component.event;
+
+import javax.faces.event.FacesListener;
+
+public interface FacetSuggestionListener extends FacesListener {
+// -------------------------- OTHER METHODS --------------------------
+
+ void suggest(FacetSuggestionEvent suggestionEvent);
+}
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/SearchEvent.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/SearchEvent.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/SearchEvent.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,26 @@
+package org.richfaces.component.event;
+
+import javax.faces.component.UIComponent;
+import javax.faces.event.FacesEvent;
+import javax.faces.event.FacesListener;
+
+public class SearchEvent extends FacesEvent {
+// --------------------------- CONSTRUCTORS ---------------------------
+
+ public SearchEvent(UIComponent component)
+ {
+ super(component);
+ }
+
+// -------------------------- OTHER METHODS --------------------------
+
+ public boolean isAppropriateListener(FacesListener facesListener)
+ {
+ return facesListener instanceof FacetSuggestionListener;
+ }
+
+ public void processListener(FacesListener facesListener)
+ {
+ ((SearchListener) facesListener).search(this);
+ }
+}
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/SearchListener.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/SearchListener.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/SearchListener.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,9 @@
+package org.richfaces.component.event;
+
+import javax.faces.event.FacesListener;
+
+public interface SearchListener extends FacesListener {
+// -------------------------- OTHER METHODS --------------------------
+
+ void search(SearchEvent searchEvent);
+}
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/ValueSuggestionEvent.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/ValueSuggestionEvent.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/ValueSuggestionEvent.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,54 @@
+package org.richfaces.component.event;
+
+import javax.faces.component.UIComponent;
+import javax.faces.event.FacesEvent;
+import javax.faces.event.FacesListener;
+
+public class ValueSuggestionEvent extends FacesEvent {
+// ------------------------------ FIELDS ------------------------------
+
+ private String facet;
+
+ private String searchTerm;
+
+// --------------------------- CONSTRUCTORS ---------------------------
+
+ public ValueSuggestionEvent(UIComponent component, String searchTerm, String facet)
+ {
+ super(component);
+ this.searchTerm = searchTerm;
+ this.facet = facet;
+ }
+
+// --------------------- GETTER / SETTER METHODS ---------------------
+
+ public String getFacet()
+ {
+ return facet;
+ }
+
+ public String getSearchTerm()
+ {
+ return searchTerm;
+ }
+
+// ------------------------ CANONICAL METHODS ------------------------
+
+ @Override
+ public String toString()
+ {
+ return "ValueSuggestionEvent{" + "facet='" + facet +
'\'' + ", text='" + searchTerm + '\'' +
'}';
+ }
+
+// -------------------------- OTHER METHODS --------------------------
+
+ public boolean isAppropriateListener(FacesListener facesListener)
+ {
+ return facesListener instanceof FacetSuggestionListener;
+ }
+
+ public void processListener(FacesListener facesListener)
+ {
+ ((ValueSuggestionListener) facesListener).suggest(this);
+ }
+}
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/ValueSuggestionListener.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/ValueSuggestionListener.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/event/ValueSuggestionListener.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,9 @@
+package org.richfaces.component.event;
+
+import javax.faces.event.FacesListener;
+
+public interface ValueSuggestionListener extends FacesListener {
+// -------------------------- OTHER METHODS --------------------------
+
+ void suggest(ValueSuggestionEvent suggestionEvent);
+}
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/package-info.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/package-info.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/component/package-info.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,4 @@
+@TagLibrary(uri = "http://richfaces.org/sandbox/visualsearch", shortName =
"visualsearch", prefix = "visualsearch",
+ displayName = "Visualsearch component tags") package
org.richfaces.component;
+
+import org.richfaces.cdk.annotations.TagLibrary;
\ No newline at end of file
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/renderkit/html/VisualsearchRenderer.java
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/renderkit/html/VisualsearchRenderer.java
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/java/org/richfaces/renderkit/html/VisualsearchRenderer.java 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,203 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright , Red Hat, Inc. and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.richfaces.renderkit.html;
+
+import org.ajax4jsf.javascript.JSFunctionDefinition;
+import org.ajax4jsf.javascript.JSObject;
+import org.ajax4jsf.javascript.JSReference;
+import org.ajax4jsf.javascript.ScriptString;
+import org.richfaces.cdk.annotations.JsfRenderer;
+import org.richfaces.component.AbstractVisualsearch;
+import org.richfaces.component.SwitchType;
+import org.richfaces.component.event.FacetSuggestionEvent;
+import org.richfaces.component.event.SearchEvent;
+import org.richfaces.component.event.ValueSuggestionEvent;
+import org.richfaces.renderkit.AjaxFunction;
+import org.richfaces.renderkit.HtmlConstants;
+import org.richfaces.renderkit.InputRendererBase;
+import org.richfaces.renderkit.util.AjaxRendererUtils;
+import org.richfaces.renderkit.util.RendererUtils;
+
+import javax.faces.application.ResourceDependencies;
+import javax.faces.application.ResourceDependency;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIInput;
+import javax.faces.component.UINamingContainer;
+import javax.faces.context.FacesContext;
+import javax.faces.context.ResponseWriter;
+import javax.faces.event.PhaseId;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@JsfRenderer(family = AbstractVisualsearch.COMPONENT_FAMILY, type =
VisualsearchRenderer.RENDERER_TYPE)
+@ResourceDependencies({@ResourceDependency(library = "javax.faces", name =
"jsf.js"), @ResourceDependency(name = "jquery.js", target =
"head"),
+ @ResourceDependency(name = "richfaces.js", target = "head"),
@ResourceDependency(name = "richfaces-event.js", target = "head"),
+ @ResourceDependency(name = "richfaces-base-component.js", target =
"head"), @ResourceDependency(name = "jquery.ui.core.js", target =
"head"),
+ @ResourceDependency(name = "jquery.ui.position.js", target =
"head"), @ResourceDependency(name = "jquery.ui.widget.js", target =
"head"),
+ @ResourceDependency(name = "jquery.ui.autocomplete.js", target =
"head"), @ResourceDependency(name = "underscore.js", target =
"head"),
+ @ResourceDependency(name = "backbone.js", target = "head"),
@ResourceDependency(name = "visualsearch.js", target = "head"),
+ @ResourceDependency(name = "richfaces.visualsearch.js", target =
"head"), @ResourceDependency(name = "visualsearch-datauri.css", target
= "head")})
+public class VisualsearchRenderer extends InputRendererBase {
+// ------------------------------ FIELDS ------------------------------
+
+ public static final String RENDERER_TYPE =
"org.richfaces.VisualsearchRenderer";
+
+ private static final String CALLBACK = "callback";
+
+ private static final Map<String, Object> DEFAULTS;
+
+ private static final String EVENT_TYPE_PARAM = "eventType";
+
+ private static final String FACET_PARAM = "facet";
+
+ private static final String QUERY_JSON_PARAM = "queryJSON";
+
+ private static final String QUERY_PARAM = "query";
+
+ private static final String SEARCH_EVENT = "search";
+
+ private static final String SEARCH_TERM_PARAM = "searchTerm";
+
+ private static final String SUGGEST_FACET_EVENT = "suggestFacet";
+
+ private static final String SUGGEST_VALUE_EVENT = "suggestValue";
+
+// -------------------------- STATIC METHODS --------------------------
+
+ static {
+ Map<String, Object> defaults = new HashMap<String, Object>();
+ DEFAULTS = Collections.unmodifiableMap(defaults);
+ }
+
+// -------------------------- OTHER METHODS --------------------------
+
+ @Override
+ public void decode(FacesContext context, UIComponent component)
+ {
+ super.decode(context, component);
+ if (!component.isRendered()) {
+ return;
+ }
+ Map<String, String> requestParameterMap =
context.getExternalContext().getRequestParameterMap();
+ if (requestParameterMap.get(component.getClientId(context)) != null) {
+ String queryParam = requestParameterMap.get(getFieldId(context,
(AbstractVisualsearch) component, QUERY_PARAM));
+ String queryJSONParam = requestParameterMap.get(getFieldId(context,
(AbstractVisualsearch) component, QUERY_JSON_PARAM));
+ String searchTermParam = requestParameterMap.get(getFieldId(context,
(AbstractVisualsearch) component, SEARCH_TERM_PARAM));
+ String facetParam = requestParameterMap.get(getFieldId(context,
(AbstractVisualsearch) component, FACET_PARAM));
+ String eventTypeParam = requestParameterMap.get(getFieldId(context,
(AbstractVisualsearch) component, EVENT_TYPE_PARAM));
+
+ if (null != queryParam) {
+ UIInput input = (UIInput) component;
+ input.setSubmittedValue(queryParam);
+ }
+
+ if (SUGGEST_VALUE_EVENT.equals(eventTypeParam)) {
+ new ValueSuggestionEvent(component, searchTermParam,
facetParam).queue();
+ } else if (SUGGEST_FACET_EVENT.equals(eventTypeParam)) {
+ new FacetSuggestionEvent(component, searchTermParam).queue();
+ } else if (SEARCH_EVENT.equals(eventTypeParam)) {
+ final SearchEvent searchEvent = new SearchEvent(component);
+ searchEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
+ searchEvent.queue();
+ }
+ }
+ }
+
+ @Override
+ public void encodeEnd(FacesContext context, UIComponent component) throws
IOException
+ {
+ if (!(component instanceof AbstractVisualsearch)) {
+ return;
+ }
+ String clientId = component.getClientId(context);
+ ResponseWriter writer = context.getResponseWriter();
+ writer.startElement(HtmlConstants.DIV_ELEM, null);
+ writer.writeAttribute(HtmlConstants.ID_ATTRIBUTE, getUtils().clientId(context,
component), "type");
+ writer.startElement(HtmlConstants.SCRIPT_ELEM, null);
+ writer.writeAttribute(HtmlConstants.TYPE_ATTR, "text/javascript",
"type");
+ final Map<String, Object> options = getOptions(context,
(AbstractVisualsearch) component);
+ options.put("submitEventFunction", createSubmitEventFunction(context,
(AbstractVisualsearch) component));
+ writer.writeText(new JSObject("RichFaces.ui.Visualsearch", clientId,
options), null);
+ writer.writeText(";", null);
+ writer.endElement(HtmlConstants.SCRIPT_ELEM);
+ writer.endElement(HtmlConstants.DIV_ELEM);
+ }
+
+ protected void addOptionIfSetAndNotDefault(String optionName, Object value,
Map<String, Object> options)
+ {
+ if (value != null && !"".equals(value) &&
!value.equals(DEFAULTS.get(
+ optionName)) && !(value instanceof Collection &&
((Collection) value).size() == 0) && !(value instanceof Map && ((Map)
value).size() == 0)) {
+ options.put(optionName, value);
+ }
+ }
+
+ protected Object createSubmitEventFunction(FacesContext context, AbstractVisualsearch
component)
+ {
+ ScriptString jsFunction;
+ Map<String, Object> params = new HashMap<String, Object>();
+ params.put(getFieldId(context, component, FACET_PARAM), new
JSReference(FACET_PARAM));
+ params.put(getFieldId(context, component, SEARCH_TERM_PARAM), new
JSReference(SEARCH_TERM_PARAM));
+ params.put(getFieldId(context, component, QUERY_PARAM), new
JSReference(QUERY_PARAM));
+ params.put(getFieldId(context, component, QUERY_JSON_PARAM), new
JSReference(QUERY_JSON_PARAM));
+ params.put(getFieldId(context, component, EVENT_TYPE_PARAM), new
JSReference(EVENT_TYPE_PARAM));
+ String clientId = component.getClientId();
+ params.put(clientId, clientId);
+ if (isAjaxMode(component)) {
+ AjaxFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(context,
component);
+ ajaxFunction.getOptions().getParameters().putAll(params);
+ ajaxFunction.getOptions().set("complete", new
JSReference(CALLBACK));
+ jsFunction = ajaxFunction;
+ } else {
+ return null;
+ }
+ return new JSFunctionDefinition("event", EVENT_TYPE_PARAM, QUERY_PARAM,
QUERY_JSON_PARAM, FACET_PARAM, SEARCH_TERM_PARAM, CALLBACK).addToBody(
+ jsFunction);
+ }
+
+ protected String getFieldId(FacesContext context, AbstractVisualsearch component,
String attribute)
+ {
+ return RendererUtils.getInstance().clientId(context, component) +
UINamingContainer.getSeparatorChar(context) + attribute;
+ }
+
+ protected Map<String, Object> getOptions(FacesContext context,
AbstractVisualsearch visualsearch) throws IOException
+ {
+ /**
+ * Include only attributes that are actually set.
+ */
+ Map<String, Object> options = new HashMap<String, Object>();
+ addOptionIfSetAndNotDefault("query", visualsearch.getQuery(),
options);
+ addOptionIfSetAndNotDefault("onsearch", visualsearch.getOnsearch(),
options);
+ addOptionIfSetAndNotDefault("facets",
visualsearch.getFacetSuggestions(), options);
+ addOptionIfSetAndNotDefault("values",
visualsearch.getValueSuggestions(), options);
+ return options;
+ }
+
+ protected boolean isAjaxMode(AbstractVisualsearch component)
+ {
+ SwitchType mode = component.getSwitchType();
+ return SwitchType.ajax.equals(mode) || null == mode;
+ }
+}
Added: sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/backbone.js
===================================================================
--- sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/backbone.js
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/backbone.js 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,1152 @@
+// Backbone.js 0.5.0-pre
+// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+//
http://documentcloud.github.com/backbone
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object.
+ var root = this;
+
+ // Save the previous value of the `Backbone` variable.
+ var previousBackbone = root.Backbone;
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.5.0-pre';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ =
require('underscore')._;
+
+ // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
+ var $ = root.jQuery || root.Zepto;
+
+ // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+ // to its previous owner. Returns a reference to this Backbone object.
+ Backbone.noConflict = function() {
+ root.Backbone = previousBackbone;
+ return this;
+ };
+
+ // Turn on `emulateHTTP` to use support legacy HTTP servers. Setting this option will
+ // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter
and set a
+ // `X-Http-Method-Override` header.
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+ // `application/json` requests ... will encode the body as
+ // `application/x-www-form-urlencoded` instead and will send the model in a
+ // form param named `model`.
+ Backbone.emulateJSON = false;
+
+ // Backbone.Events
+ // -----------------
+
+ // A module that can be mixed in to *any object* in order to provide it with
+ // custom events. You may `bind` or `unbind` a callback function to an event;
+ // `trigger`-ing an event fires all callbacks in succession.
+ //
+ // var object = {};
+ // _.extend(object, Backbone.Events);
+ // object.bind('expand', function(){ alert('expanded'); });
+ // object.trigger('expand');
+ //
+ Backbone.Events = {
+
+ // Bind an event, specified by a string name, `ev`, to a `callback` function.
+ // Passing `"all"` will bind the callback to all events fired.
+ bind : function(ev, callback) {
+ var calls = this._callbacks || (this._callbacks = {});
+ var list = calls[ev] || (calls[ev] = []);
+ list.push(callback);
+ return this;
+ },
+
+ // Remove one or many callbacks. If `callback` is null, removes all
+ // callbacks for the event. If `ev` is null, removes all bound callbacks
+ // for all events.
+ unbind : function(ev, callback) {
+ var calls;
+ if (!ev) {
+ this._callbacks = {};
+ } else if (calls = this._callbacks) {
+ if (!callback) {
+ calls[ev] = [];
+ } else {
+ var list = calls[ev];
+ if (!list) return this;
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (callback === list[i]) {
+ list[i] = null;
+ break;
+ }
+ }
+ }
+ }
+ return this;
+ },
+
+ // Trigger an event, firing all bound callbacks. Callbacks are passed the
+ // same arguments as `trigger` is, apart from the event name.
+ // Listening for `"all"` passes the true event name as the first argument.
+ trigger : function(eventName) {
+ var list, calls, ev, callback, args;
+ var both = 2;
+ if (!(calls = this._callbacks)) return this;
+ while (both--) {
+ ev = both ? eventName : 'all';
+ if (list = calls[ev]) {
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (!(callback = list[i])) {
+ list.splice(i, 1); i--; l--;
+ } else {
+ args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
+ callback.apply(this, args);
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ };
+
+ // Backbone.Model
+ // --------------
+
+ // Create a new model, with defined attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ Backbone.Model = function(attributes, options) {
+ var defaults;
+ attributes || (attributes = {});
+ if (defaults = this.defaults) {
+ if (_.isFunction(defaults)) defaults = defaults();
+ attributes = _.extend({}, defaults, attributes);
+ }
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.set(attributes, {silent : true});
+ this._changed = false;
+ this._previousAttributes = _.clone(this.attributes);
+ if (options && options.collection) this.collection = options.collection;
+ this.initialize.apply(this, arguments);
+ };
+
+ // Attach all inheritable methods to the Model prototype.
+ _.extend(Backbone.Model.prototype, Backbone.Events, {
+
+ // A snapshot of the model's previous attributes, taken immediately
+ // after the last `"change"` event was fired.
+ _previousAttributes : null,
+
+ // Has the item been changed since the last `"change"` event?
+ _changed : false,
+
+ // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+ // CouchDB users may want to set this to `"_id"`.
+ idAttribute : 'id',
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize : function(){},
+
+ // Return a copy of the model's `attributes` object.
+ toJSON : function() {
+ return _.clone(this.attributes);
+ },
+
+ // Get the value of an attribute.
+ get : function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape : function(attr) {
+ var html;
+ if (html = this._escapedAttributes[attr]) return html;
+ var val = this.attributes[attr];
+ return this._escapedAttributes[attr] = escapeHTML(val == null ? '' :
'' + val);
+ },
+
+ // Returns `true` if the attribute contains a value that is not null
+ // or undefined.
+ has : function(attr) {
+ return this.attributes[attr] != null;
+ },
+
+ // Set a hash of model attributes on the object, firing `"change"` unless
you
+ // choose to silence it.
+ set : function(attrs, options) {
+
+ // Extract attributes and options.
+ options || (options = {});
+ if (!attrs) return this;
+ if (attrs.attributes) attrs = attrs.attributes;
+ var now = this.attributes, escaped = this._escapedAttributes;
+
+ // Run validation.
+ if (!options.silent && this.validate &&
!this._performValidation(attrs, options)) return false;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ // We're about to start triggering change events.
+ var alreadyChanging = this._changing;
+ this._changing = true;
+
+ // Update attributes.
+ for (var attr in attrs) {
+ var val = attrs[attr];
+ if (!_.isEqual(now[attr], val)) {
+ now[attr] = val;
+ delete escaped[attr];
+ this._changed = true;
+ if (!options.silent) this.trigger('change:' + attr, this, val,
options);
+ }
+ }
+
+ // Fire the `"change"` event, if the model has been changed.
+ if (!alreadyChanging && !options.silent && this._changed)
this.change(options);
+ this._changing = false;
+ return this;
+ },
+
+ // Remove an attribute from the model, firing `"change"` unless you choose
+ // to silence it. `unset` is a noop if the attribute doesn't exist.
+ unset : function(attr, options) {
+ if (!(attr in this.attributes)) return this;
+ options || (options = {});
+ var value = this.attributes[attr];
+
+ // Run validation.
+ var validObj = {};
+ validObj[attr] = void 0;
+ if (!options.silent && this.validate &&
!this._performValidation(validObj, options)) return false;
+
+ // Remove the attribute.
+ delete this.attributes[attr];
+ delete this._escapedAttributes[attr];
+ if (attr == this.idAttribute) delete this.id;
+ this._changed = true;
+ if (!options.silent) {
+ this.trigger('change:' + attr, this, void 0, options);
+ this.change(options);
+ }
+ return this;
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear : function(options) {
+ options || (options = {});
+ var old = this.attributes;
+
+ // Run validation.
+ var validObj = {};
+ for (var attr in old) validObj[attr] = void 0;
+ if (!options.silent && this.validate &&
!this._performValidation(validObj, options)) return false;
+
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this._changed = true;
+ if (!options.silent) {
+ for (var attr in old) {
+ this.trigger('change:' + attr, this, void 0, options);
+ }
+ this.change(options);
+ }
+ return this;
+ },
+
+ // Fetch the model from the server. If the server's representation of the
+ // model differs from its current attributes, they will be overriden,
+ // triggering a `"change"` event.
+ fetch : function(options) {
+ options || (options = {});
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (success) success(model, resp);
+ };
+ options.error = wrapError(options.error, model, options);
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // Set a hash of model attributes, and sync the model to the server.
+ // If the server returns an attributes hash that differs, the model's
+ // state will be `set` again.
+ save : function(attrs, options) {
+ options || (options = {});
+ if (attrs && !this.set(attrs, options)) return false;
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (success) success(model, resp, xhr);
+ };
+ options.error = wrapError(options.error, model, options);
+ var method = this.isNew() ? 'create' : 'update';
+ return (this.sync || Backbone.sync).call(this, method, this, options);
+ },
+
+ // Destroy this model on the server if it was already persisted. Upon success, the
model is removed
+ // from its collection, if it has one.
+ destroy : function(options) {
+ options || (options = {});
+ if (this.isNew()) return this.trigger('destroy', this, this.collection,
options);
+ var model = this;
+ var success = options.success;
+ options.success = function(resp) {
+ model.trigger('destroy', model, model.collection, options);
+ if (success) success(model, resp);
+ };
+ options.error = wrapError(options.error, model, options);
+ return (this.sync || Backbone.sync).call(this, 'delete', this, options);
+ },
+
+ // Default URL for the model's representation on the server -- if you're
+ // using Backbone's restful methods, override this to change the endpoint
+ // that will be called.
+ url : function() {
+ var base = getUrl(this.collection) || this.urlRoot || urlError();
+ if (this.isNew()) return base;
+ return base + (base.charAt(base.length - 1) == '/' ? '' :
'/') + encodeURIComponent(this.id);
+ },
+
+ // **parse** converts a response into the hash of attributes to be `set` on
+ // the model. The default implementation is just to pass the response along.
+ parse : function(resp, xhr) {
+ return resp;
+ },
+
+ // Create a new model with identical attributes to this one.
+ clone : function() {
+ return new this.constructor(this);
+ },
+
+ // A model is new if it has never been saved to the server, and lacks an id.
+ isNew : function() {
+ return this.id == null;
+ },
+
+ // Call this method to manually fire a `change` event for this model.
+ // Calling this will cause all objects observing the model to update.
+ change : function(options) {
+ this.trigger('change', this, options);
+ this._previousAttributes = _.clone(this.attributes);
+ this._changed = false;
+ },
+
+ // Determine if the model has changed since the last `"change"` event.
+ // If you specify an attribute name, determine if that attribute has changed.
+ hasChanged : function(attr) {
+ if (attr) return this._previousAttributes[attr] != this.attributes[attr];
+ return this._changed;
+ },
+
+ // Return an object containing all the attributes that have changed, or false
+ // if there are no changed attributes. Useful for determining what parts of a
+ // view need to be updated and/or what attributes need to be persisted to
+ // the server.
+ changedAttributes : function(now) {
+ now || (now = this.attributes);
+ var old = this._previousAttributes;
+ var changed = false;
+ for (var attr in now) {
+ if (!_.isEqual(old[attr], now[attr])) {
+ changed = changed || {};
+ changed[attr] = now[attr];
+ }
+ }
+ return changed;
+ },
+
+ // Get the previous value of an attribute, recorded at the time the last
+ // `"change"` event was fired.
+ previous : function(attr) {
+ if (!attr || !this._previousAttributes) return null;
+ return this._previousAttributes[attr];
+ },
+
+ // Get all of the attributes of the model at the time of the previous
+ // `"change"` event.
+ previousAttributes : function() {
+ return _.clone(this._previousAttributes);
+ },
+
+ // Run validation against a set of incoming attributes, returning `true`
+ // if all is well. If a specific `error` callback has been passed,
+ // call that instead of firing the general `"error"` event.
+ _performValidation : function(attrs, options) {
+ var error = this.validate(attrs);
+ if (error) {
+ if (options.error) {
+ options.error(this, error, options);
+ } else {
+ this.trigger('error', this, error, options);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ });
+
+ // Backbone.Collection
+ // -------------------
+
+ // Provides a standard collection class for our sets of models, ordered
+ // or unordered. If a `comparator` is specified, the Collection will maintain
+ // its models in sort order, as they're added and removed.
+ Backbone.Collection = function(models, options) {
+ options || (options = {});
+ if (options.comparator) this.comparator = options.comparator;
+ _.bindAll(this, '_onModelEvent', '_removeReference');
+ this._reset();
+ if (models) this.reset(models, {silent: true});
+ this.initialize.apply(this, arguments);
+ };
+
+ // Define the Collection's inheritable methods.
+ _.extend(Backbone.Collection.prototype, Backbone.Events, {
+
+ // The default model for a collection is just a **Backbone.Model**.
+ // This should be overridden in most cases.
+ model : Backbone.Model,
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize : function(){},
+
+ // The JSON representation of a Collection is an array of the
+ // models' attributes.
+ toJSON : function() {
+ return this.map(function(model){ return model.toJSON(); });
+ },
+
+ // Add a model, or list of models to the set. Pass **silent** to avoid
+ // firing the `added` event for every new model.
+ add : function(models, options) {
+ if (_.isArray(models)) {
+ for (var i = 0, l = models.length; i < l; i++) {
+ this._add(models[i], options);
+ }
+ } else {
+ this._add(models, options);
+ }
+ return this;
+ },
+
+ // Remove a model, or a list of models from the set. Pass silent to avoid
+ // firing the `removed` event for every model removed.
+ remove : function(models, options) {
+ if (_.isArray(models)) {
+ for (var i = 0, l = models.length; i < l; i++) {
+ this._remove(models[i], options);
+ }
+ } else {
+ this._remove(models, options);
+ }
+ return this;
+ },
+
+ // Get a model from the set by id.
+ get : function(id) {
+ if (id == null) return null;
+ return this._byId[id.id != null ? id.id : id];
+ },
+
+ // Get a model from the set by client id.
+ getByCid : function(cid) {
+ return cid && this._byCid[cid.cid || cid];
+ },
+
+ // Get the model at the given index.
+ at: function(index) {
+ return this.models[index];
+ },
+
+ // Force the collection to re-sort itself. You don't need to call this under
normal
+ // circumstances, as the set will maintain sort order as each item is added.
+ sort : function(options) {
+ options || (options = {});
+ if (!this.comparator) throw new Error('Cannot sort a set without a
comparator');
+ this.models = this.sortBy(this.comparator);
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Pluck an attribute from each model in the collection.
+ pluck : function(attr) {
+ return _.map(this.models, function(model){ return model.get(attr); });
+ },
+
+ // When you have more items than you want to add or remove individually,
+ // you can reset the entire set with a new list of models, without firing
+ // any `added` or `removed` events. Fires `reset` when finished.
+ reset : function(models, options) {
+ models || (models = []);
+ options || (options = {});
+ this.each(this._removeReference);
+ this._reset();
+ this.add(models, {silent: true});
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Fetch the default set of models for this collection, resetting the
+ // collection when they arrive. If `add: true` is passed, appends the
+ // models to the collection instead of resetting.
+ fetch : function(options) {
+ options || (options = {});
+ var collection = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ collection[options.add ? 'add' : 'reset'](collection.parse(resp,
xhr), options);
+ if (success) success(collection, resp);
+ };
+ options.error = wrapError(options.error, collection, options);
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // Create a new instance of a model in this collection. After the model
+ // has been created on the server, it will be added to the collection.
+ // Returns the model, or 'false' if validation on a new model fails.
+ create : function(model, options) {
+ var coll = this;
+ options || (options = {});
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ var success = options.success;
+ options.success = function(nextModel, resp, xhr) {
+ coll.add(nextModel, options);
+ if (success) success(nextModel, resp, xhr);
+ };
+ model.save(null, options);
+ return model;
+ },
+
+ // **parse** converts a response into a list of models to be added to the
+ // collection. The default implementation is just to pass it through.
+ parse : function(resp, xhr) {
+ return resp;
+ },
+
+ // Proxy to _'s chain. Can't be proxied the same way the rest of the
+ // underscore methods are proxied because it relies on the underscore
+ // constructor.
+ chain: function () {
+ return _(this.models).chain();
+ },
+
+ // Reset all internal state. Called when the collection is refreshed.
+ _reset : function(options) {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ // Prepare a model to be added to this collection
+ _prepareModel: function(model, options) {
+ if (!(model instanceof Backbone.Model)) {
+ var attrs = model;
+ model = new this.model(attrs, {collection: this});
+ if (model.validate && !model._performValidation(attrs, options)) model =
false;
+ } else if (!model.collection) {
+ model.collection = this;
+ }
+ return model;
+ },
+
+ // Internal implementation of adding a single model to the set, updating
+ // hash indexes for `id` and `cid` lookups.
+ // Returns the model, or 'false' if validation on a new model fails.
+ _add : function(model, options) {
+ options || (options = {});
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ var already = this.getByCid(model) || this.get(model);
+ if (already) throw new Error(["Can't add the same model to a set
twice", already.id]);
+ this._byId[model.id] = model;
+ this._byCid[model.cid] = model;
+ var index = options.at != null ? options.at :
+ this.comparator ? this.sortedIndex(model, this.comparator) :
+ this.length;
+ this.models.splice(index, 0, model);
+ model.bind('all', this._onModelEvent);
+ this.length++;
+ if (!options.silent) model.trigger('add', model, this, options);
+ return model;
+ },
+
+ // Internal implementation of removing a single model from the set, updating
+ // hash indexes for `id` and `cid` lookups.
+ _remove : function(model, options) {
+ options || (options = {});
+ model = this.getByCid(model) || this.get(model);
+ if (!model) return null;
+ delete this._byId[model.id];
+ delete this._byCid[model.cid];
+ this.models.splice(this.indexOf(model), 1);
+ this.length--;
+ if (!options.silent) model.trigger('remove', model, this, options);
+ this._removeReference(model);
+ return model;
+ },
+
+ // Internal method to remove a model's ties to a collection.
+ _removeReference : function(model) {
+ if (this == model.collection) {
+ delete model.collection;
+ }
+ model.unbind('all', this._onModelEvent);
+ },
+
+ // Internal method called every time a model in the set fires an event.
+ // Sets need to update their indexes when models change ids. All other
+ // events simply proxy through. "add" and "remove" events that
originate
+ // in other collections are ignored.
+ _onModelEvent : function(ev, model, collection, options) {
+ if ((ev == 'add' || ev == 'remove') && collection != this)
return;
+ if (ev == 'destroy') {
+ this._remove(model, options);
+ }
+ if (model && ev === 'change:' + model.idAttribute) {
+ delete this._byId[model.previous(model.idAttribute)];
+ this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+ });
+
+ // Underscore methods that we want to implement on the Collection.
+ var methods = ['forEach', 'each', 'map', 'reduce',
'reduceRight', 'find', 'detect',
+ 'filter', 'select', 'reject', 'every', 'all',
'some', 'any', 'include',
+ 'invoke', 'max', 'min', 'sortBy',
'sortedIndex', 'toArray', 'size',
+ 'first', 'rest', 'last', 'without',
'indexOf', 'lastIndexOf', 'isEmpty'];
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ _.each(methods, function(method) {
+ Backbone.Collection.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
+ };
+ });
+
+ // Backbone.Router
+ // -------------------
+
+ // Routers map faux-URLs to actions, and fire events when routes are
+ // matched. Creating a new one sets its `routes` hash, if not set statically.
+ Backbone.Router = function(options) {
+ options || (options = {});
+ if (options.routes) this.routes = options.routes;
+ this._bindRoutes();
+ this.initialize.apply(this, arguments);
+ };
+
+ // Cached regular expressions for matching named param parts and splatted
+ // parts of route strings.
+ var namedParam = /:([\w\d]+)/g;
+ var splatParam = /\*([\w\d]+)/g;
+ var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+ // Set up all inheritable **Backbone.Router** properties and methods.
+ _.extend(Backbone.Router.prototype, Backbone.Events, {
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize : function(){},
+
+ // Manually bind a single named route to a callback. For example:
+ //
+ // this.route('search/:query/p:num', 'search', function(query,
num) {
+ // ...
+ // });
+ //
+ route : function(route, name, callback) {
+ Backbone.history || (Backbone.history = new Backbone.History);
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+ Backbone.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ callback.apply(this, args);
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ }, this));
+ },
+
+ // Simple proxy to `Backbone.history` to save a fragment into the history,
+ // without triggering routes.
+ saveLocation : function(fragment) {
+ Backbone.history.saveLocation(fragment);
+ },
+
+ // Simple proxy to `Backbone.history` to both save a fragment into the
+ // history and to then load the route at that fragment.
+ setLocation : function(fragment) {
+ Backbone.history.saveLocation(fragment);
+ Backbone.history.loadUrl(fragment);
+ },
+
+ // Bind all defined routes to `Backbone.history`. We have to reverse the
+ // order of the routes here to support behavior where the most general
+ // routes can be defined at the bottom of the route map.
+ _bindRoutes : function() {
+ if (!this.routes) return;
+ var routes = [];
+ for (var route in this.routes) {
+ routes.unshift([route, this.routes[route]]);
+ }
+ for (var i = 0, l = routes.length; i < l; i++) {
+ this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp : function(route) {
+ route = route.replace(escapeRegExp, "\\$&")
+ .replace(namedParam, "([^\/]*)")
+ .replace(splatParam, "(.*?)");
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters : function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+
+ });
+
+ // Backbone.History
+ // ----------------
+
+ // Handles cross-browser history management, based on URL fragments. If the
+ // browser does not support `onhashchange`, falls back to polling.
+ Backbone.History = function() {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+ };
+
+ // Cached regex for cleaning hashes.
+ var hashStrip = /^#*!?/;
+
+ // Cached regex for detecting MSIE.
+ var isExplorer = /msie [\w.]+/;
+
+ // Has the history handling already been started?
+ var historyStarted = false;
+
+ // Set up all inheritable **Backbone.History** properties and methods.
+ _.extend(Backbone.History.prototype, {
+
+ // The default interval to poll for hash changes, if necessary, is
+ // twenty times a second.
+ interval: 50,
+
+ // Get the cross-browser normalized URL fragment, either from the URL,
+ // the hash, or the override.
+ getFragment : function(fragment, forcePushState) {
+ if (fragment == null) {
+ if (this._hasPushState || forcePushState) {
+ fragment = window.location.pathname;
+ var search = window.location.search;
+ if (search) fragment += search;
+ if (fragment.indexOf(this.options.root) == 0) fragment =
fragment.substr(this.options.root.length);
+ } else {
+ fragment = window.location.hash;
+ }
+ }
+ return fragment.replace(hashStrip, '');
+ },
+
+ // Start the hash change handling, returning `true` if the current URL matches
+ // an existing route, and `false` otherwise.
+ start : function(options) {
+
+ // Figure out the initial configuration. Do we need an iframe?
+ // Is pushState desired ... is it available?
+ if (historyStarted) throw new Error("Backbone.history has already been
started");
+ this.options = _.extend({}, {root: '/'}, this.options, options);
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState && window.history
&& window.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase())
&& (!docMode || docMode <= 7));
+ if (oldIE) {
+ this.iframe = $('<iframe src="javascript:0"
tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
+ this.saveLocation(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ $(window).bind('popstate', this.checkUrl);
+ } else if ('onhashchange' in window && !oldIE) {
+ $(window).bind('hashchange', this.checkUrl);
+ } else {
+ setInterval(this.checkUrl, this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ historyStarted = true;
+ var started = this.loadUrl() || this.loadUrl(window.location.hash);
+ if (this._wantsPushState && !this._hasPushState &&
window.location.pathname != this.options.root) {
+ this.fragment = this.getFragment(null, true);
+ window.location = this.options.root + '#' + this.fragment;
+ } else {
+ return started;
+ }
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later may
+ // override previous routes.
+ route : function(route, callback) {
+ this.handlers.unshift({route : route, callback : callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl : function(e) {
+ var current = this.getFragment();
+ if (current == this.fragment && this.iframe) current =
this.getFragment(this.iframe.location.hash);
+ if (current == this.fragment || current == decodeURIComponent(this.fragment))
return false;
+ if (this.iframe) this.saveLocation(current);
+ this.loadUrl() || this.loadUrl(window.location.hash);
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl : function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history. You are responsible for properly
+ // URL-encoding the fragment in advance. This does not trigger
+ // a `hashchange` event.
+ saveLocation : function(fragment) {
+ fragment = (fragment || '').replace(hashStrip, '');
+ if (this.fragment == fragment || this.fragment == decodeURIComponent(fragment))
return;
+ if (this._hasPushState) {
+ var loc = window.location;
+ if (fragment.indexOf(this.options.root) != 0) fragment = this.options.root +
fragment;
+ this.fragment = fragment;
+ window.history.pushState({}, document.title, loc.protocol + '//' +
loc.host + fragment);
+ } else {
+ window.location.hash = this.fragment = fragment;
+ if (this.iframe && (fragment !=
this.getFragment(this.iframe.location.hash))) {
+ this.iframe.document.open().close();
+ this.iframe.location.hash = fragment;
+ }
+ }
+ }
+
+ });
+
+ // Backbone.View
+ // -------------
+
+ // Creating a Backbone.View creates its initial element outside of the DOM,
+ // if an existing element is not provided...
+ Backbone.View = function(options) {
+ this.cid = _.uniqueId('view');
+ this._configure(options || {});
+ this._ensureElement();
+ this.delegateEvents();
+ this.initialize.apply(this, arguments);
+ };
+
+ // Element lookup, scoped to DOM elements within the current view.
+ // This should be prefered to global lookups, if you're dealing with
+ // a specific view.
+ var selectorDelegate = function(selector) {
+ return $(selector, this.el);
+ };
+
+ // Cached regex to split keys for `delegate`.
+ var eventSplitter = /^(\S+)\s*(.*)$/;
+
+ // List of view options to be merged as properties.
+ var viewOptions = ['model', 'collection', 'el', 'id',
'attributes', 'className', 'tagName'];
+
+ // Set up all inheritable **Backbone.View** properties and methods.
+ _.extend(Backbone.View.prototype, Backbone.Events, {
+
+ // The default `tagName` of a View's element is `"div"`.
+ tagName : 'div',
+
+ // Attach the `selectorDelegate` function as the `$` property.
+ $ : selectorDelegate,
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize : function(){},
+
+ // **render** is the core function that your view should override, in order
+ // to populate its element (`this.el`), with the appropriate HTML. The
+ // convention is for **render** to always return `this`.
+ render : function() {
+ return this;
+ },
+
+ // Remove this view from the DOM. Note that the view isn't present in the
+ // DOM by default, so calling this method may be a no-op.
+ remove : function() {
+ $(this.el).remove();
+ return this;
+ },
+
+ // For small amounts of DOM Elements, where a full-blown template isn't
+ // needed, use **make** to manufacture elements, one at a time.
+ //
+ // var el = this.make('li', {'class': 'row'},
this.model.escape('title'));
+ //
+ make : function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) $(el).attr(attributes);
+ if (content) $(el).html(content);
+ return el;
+ },
+
+ // Set callbacks, where `this.callbacks` is a hash of
+ //
+ // *{"event selector": "callback"}*
+ //
+ // {
+ // 'mousedown .title': 'edit',
+ // 'click .button': 'save'
+ // }
+ //
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
+ // Uses event delegation for efficiency.
+ // Omitting the selector binds the event to `this.el`.
+ // This only works for delegate-able events: not `focus`, `blur`, and
+ // not `change`, `submit`, and `reset` in Internet Explorer.
+ delegateEvents : function(events) {
+ if (!(events || (events = this.events))) return;
+ $(this.el).unbind('.delegateEvents' + this.cid);
+ for (var key in events) {
+ var method = this[events[key]];
+ if (!method) throw new Error('Event "' + events[key] + '"
does not exist');
+ var match = key.match(eventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, this);
+ eventName += '.delegateEvents' + this.cid;
+ if (selector === '') {
+ $(this.el).bind(eventName, method);
+ } else {
+ $(this.el).delegate(selector, eventName, method);
+ }
+ }
+ },
+
+ // Performs the initial configuration of a View with a set of options.
+ // Keys with special meaning *(model, collection, id, className)*, are
+ // attached directly to the view.
+ _configure : function(options) {
+ if (this.options) options = _.extend({}, this.options, options);
+ for (var i = 0, l = viewOptions.length; i < l; i++) {
+ var attr = viewOptions[i];
+ if (options[attr]) this[attr] = options[attr];
+ }
+ this.options = options;
+ },
+
+ // Ensure that the View has a DOM element to render into.
+ // If `this.el` is a string, pass it through `$()`, take the first
+ // matching element, and re-assign it to `el`. Otherwise, create
+ // an element from the `id`, `className` and `tagName` proeprties.
+ _ensureElement : function() {
+ if (!this.el) {
+ var attrs = this.attributes || {};
+ if (this.id) attrs.id = this.id;
+ if (this.className) attrs['class'] = this.className;
+ this.el = this.make(this.tagName, attrs);
+ } else if (_.isString(this.el)) {
+ this.el = $(this.el).get(0);
+ }
+ }
+
+ });
+
+ // The self-propagating extend function that Backbone classes use.
+ var extend = function (protoProps, classProps) {
+ var child = inherits(this, protoProps, classProps);
+ child.extend = this.extend;
+ return child;
+ };
+
+ // Set up inheritance for the model, collection, and view.
+ Backbone.Model.extend = Backbone.Collection.extend =
+ Backbone.Router.extend = Backbone.View.extend = extend;
+
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+ var methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'delete': 'DELETE',
+ 'read' : 'GET'
+ };
+
+ // Backbone.sync
+ // -------------
+
+ // Override this function to change the manner in which Backbone persists
+ // models to the server. You will be passed the type of request, and the
+ // model in question. By default, uses makes a RESTful Ajax request
+ // to the model's `url()`. Some possible customizations could be:
+ //
+ // * Use `setTimeout` to batch rapid-fire updates into a single request.
+ // * Send up the models as XML instead of JSON.
+ // * Persist models via WebSockets instead of Ajax.
+ //
+ // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+ // as `POST`, with a `_method` parameter containing the true HTTP method,
+ // as well as all requests with the body as `application/x-www-form-urlencoded` instead
of
+ // `application/json` with the model in a param named `model`.
+ // Useful when interfacing with server-side languages like **PHP** that make
+ // it difficult to read the body of `PUT` requests.
+ Backbone.sync = function(method, model, options) {
+ var type = methodMap[method];
+
+ // Default JSON-request options.
+ var params = _.extend({
+ type: type,
+ dataType: 'json',
+ processData: false
+ }, options);
+
+ // Ensure that we have a URL.
+ if (!params.url) {
+ params.url = getUrl(model) || urlError();
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (!params.data && model && (method == 'create' || method ==
'update')) {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(model.toJSON());
+ }
+
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
+ if (Backbone.emulateJSON) {
+ params.contentType = 'application/x-www-form-urlencoded';
+ params.processData = true;
+ params.data = params.data ? {model : params.data} : {};
+ }
+
+ // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+ // And an `X-HTTP-Method-Override` header.
+ if (Backbone.emulateHTTP) {
+ if (type === 'PUT' || type === 'DELETE') {
+ if (Backbone.emulateJSON) params.data._method = type;
+ params.type = 'POST';
+ params.beforeSend = function(xhr) {
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
+ };
+ }
+ }
+
+ // Make the request.
+ return $.ajax(params);
+ };
+
+ // Helpers
+ // -------
+
+ // Shared empty constructor function to aid in prototype-chain creation.
+ var ctor = function(){};
+
+ // Helper function to correctly set up the prototype chain, for subclasses.
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
+ // class properties to be extended.
+ var inherits = function(parent, protoProps, staticProps) {
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call `super()`.
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ return parent.apply(this, arguments); };
+ }
+
+ // Inherit class (static) properties from parent.
+ _.extend(child, parent);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) _.extend(child.prototype, protoProps);
+
+ // Add static properties to the constructor function, if supplied.
+ if (staticProps) _.extend(child, staticProps);
+
+ // Correctly set child's `prototype.constructor`.
+ child.prototype.constructor = child;
+
+ // Set a convenience property in case the parent's prototype is needed later.
+ child.__super__ = parent.prototype;
+
+ return child;
+ };
+
+ // Helper function to get a URL from a Model or Collection as a property
+ // or as a function.
+ var getUrl = function(object) {
+ if (!(object && object.url)) return null;
+ return _.isFunction(object.url) ? object.url() : object.url;
+ };
+
+ // Throw an error when a URL is needed, and none is supplied.
+ var urlError = function() {
+ throw new Error('A "url" property or function must be specified');
+ };
+
+ // Wrap an optional error callback with a fallback error event.
+ var wrapError = function(onError, model, options) {
+ return function(resp) {
+ if (onError) {
+ onError(model, resp, options);
+ } else {
+ model.trigger('error', model, resp, options);
+ }
+ };
+ };
+
+ // Helper function to escape a string for HTML rendering.
+ var escapeHTML = function(string) {
+ return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi,
'&').replace(/</g, '<').replace(/>/g,
'>').replace(/"/g, '"').replace(/'/g,
''').replace(/\//g,'/');
+ };
+
+}).call(this);
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.autocomplete.js
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.autocomplete.js
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.autocomplete.js 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,613 @@
+/*
+ * jQuery UI Autocomplete 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (
http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
http://jquery.org/license
+ *
+ *
http://docs.jquery.com/UI/Autocomplete
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.position.js
+ */
+(function( $, undefined ) {
+
+// used to prevent race conditions with remote data sources
+var requestIndex = 0;
+
+$.widget( "ui.autocomplete", {
+ options: {
+ appendTo: "body",
+ autoFocus: false,
+ delay: 300,
+ minLength: 1,
+ position: {
+ my: "left top",
+ at: "left bottom",
+ collision: "none"
+ },
+ source: null
+ },
+
+ pending: 0,
+
+ _create: function() {
+ var self = this,
+ doc = this.element[ 0 ].ownerDocument,
+ suppressKeyPress;
+
+ this.element
+ .addClass( "ui-autocomplete-input" )
+ .attr( "autocomplete", "off" )
+ // TODO verify these actually work as intended
+ .attr({
+ role: "textbox",
+ "aria-autocomplete": "list",
+ "aria-haspopup": "true"
+ })
+ .bind( "keydown.autocomplete", function( event ) {
+ if ( self.options.disabled || self.element.attr( "readonly" ) ) {
+ return;
+ }
+
+ suppressKeyPress = false;
+ var keyCode = $.ui.keyCode;
+ switch( event.keyCode ) {
+ case keyCode.PAGE_UP:
+ self._move( "previousPage", event );
+ break;
+ case keyCode.PAGE_DOWN:
+ self._move( "nextPage", event );
+ break;
+ case keyCode.UP:
+ self._move( "previous", event );
+ // prevent moving cursor to beginning of text field in some browsers
+ event.preventDefault();
+ break;
+ case keyCode.DOWN:
+ self._move( "next", event );
+ // prevent moving cursor to end of text field in some browsers
+ event.preventDefault();
+ break;
+ case keyCode.ENTER:
+ case keyCode.NUMPAD_ENTER:
+ // when menu is open and has focus
+ if ( self.menu.active ) {
+ // #6055 - Opera still allows the keypress to occur
+ // which causes forms to submit
+ suppressKeyPress = true;
+ event.preventDefault();
+ }
+ //passthrough - ENTER and TAB both select the current element
+ case keyCode.TAB:
+ if ( !self.menu.active ) {
+ return;
+ }
+ self.menu.select( event );
+ break;
+ case keyCode.ESCAPE:
+ self.element.val( self.term );
+ self.close( event );
+ break;
+ default:
+ // keypress is triggered before the input value is changed
+ clearTimeout( self.searching );
+ self.searching = setTimeout(function() {
+ // only search if the value has changed
+ if ( self.term != self.element.val() ) {
+ self.selectedItem = null;
+ self.search( null, event );
+ }
+ }, self.options.delay );
+ break;
+ }
+ })
+ .bind( "keypress.autocomplete", function( event ) {
+ if ( suppressKeyPress ) {
+ suppressKeyPress = false;
+ event.preventDefault();
+ }
+ })
+ .bind( "focus.autocomplete", function() {
+ if ( self.options.disabled ) {
+ return;
+ }
+
+ self.selectedItem = null;
+ self.previous = self.element.val();
+ })
+ .bind( "blur.autocomplete", function( event ) {
+ if ( self.options.disabled ) {
+ return;
+ }
+
+ clearTimeout( self.searching );
+ // clicks on the menu (or a button to trigger a search) will cause a blur event
+ self.closing = setTimeout(function() {
+ self.close( event );
+ self._change( event );
+ }, 150 );
+ });
+ this._initSource();
+ this.response = function() {
+ return self._response.apply( self, arguments );
+ };
+ this.menu = $( "<ul></ul>" )
+ .addClass( "ui-autocomplete" )
+ .appendTo( $( this.options.appendTo || "body", doc )[0] )
+ // prevent the close-on-blur in case of a "slow" click on the menu (long
mousedown)
+ .mousedown(function( event ) {
+ // 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 = self.menu.element[ 0 ];
+ if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
+ setTimeout(function() {
+ $( document ).one( 'mousedown', function( event ) {
+ if ( event.target !== self.element[ 0 ] &&
+ event.target !== menuElement &&
+ !$.ui.contains( menuElement, event.target ) ) {
+ self.close();
+ }
+ });
+ }, 1 );
+ }
+
+ // use another timeout to make sure the blur-event-handler on the input was already
triggered
+ setTimeout(function() {
+ clearTimeout( self.closing );
+ }, 13);
+ })
+ .menu({
+ focus: function( event, ui ) {
+ var item = ui.item.data( "item.autocomplete" );
+ if ( false !== self._trigger( "focus", event, { item: item } ) ) {
+ // use value to match what will end up in the input, if it was a key event
+ if ( /^key/.test(event.originalEvent.type) ) {
+ self.element.val( item.value );
+ }
+ }
+ },
+ selected: function( event, ui ) {
+ var item = ui.item.data( "item.autocomplete" ),
+ previous = self.previous;
+
+ // only trigger when focus was lost (click on menu)
+ if ( self.element[0] !== doc.activeElement ) {
+ self.element.focus();
+ self.previous = previous;
+ // #6109 - IE triggers two focus events and the second
+ // is asynchronous, so we need to reset the previous
+ // term synchronously and asynchronously :-(
+ setTimeout(function() {
+ self.previous = previous;
+ self.selectedItem = item;
+ }, 1);
+ }
+
+ if ( false !== self._trigger( "select", event, { item: item } ) ) {
+ self.element.val( item.value );
+ }
+ // reset the term after the select event
+ // this allows custom select handling to work properly
+ self.term = self.element.val();
+
+ self.close( event );
+ self.selectedItem = item;
+ },
+ blur: function( event, ui ) {
+ // don't set the value of the text field if it's already correct
+ // this prevents moving the cursor unnecessarily
+ if ( self.menu.element.is(":visible") &&
+ ( self.element.val() !== self.term ) ) {
+ self.element.val( self.term );
+ }
+ }
+ })
+ .zIndex( this.element.zIndex() + 1 )
+ // workaround for jQuery bug #5781
http://dev.jquery.com/ticket/5781
+ .css({ top: 0, left: 0 })
+ .hide()
+ .data( "menu" );
+ if ( $.fn.bgiframe ) {
+ this.menu.element.bgiframe();
+ }
+ },
+
+ destroy: function() {
+ this.element
+ .removeClass( "ui-autocomplete-input" )
+ .removeAttr( "autocomplete" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-autocomplete" )
+ .removeAttr( "aria-haspopup" );
+ this.menu.element.remove();
+ $.Widget.prototype.destroy.call( this );
+ },
+
+ _setOption: function( key, value ) {
+ $.Widget.prototype._setOption.apply( this, arguments );
+ if ( key === "source" ) {
+ this._initSource();
+ }
+ if ( key === "appendTo" ) {
+ this.menu.element.appendTo( $( value || "body",
this.element[0].ownerDocument )[0] )
+ }
+ if ( key === "disabled" && value && this.xhr ) {
+ this.xhr.abort();
+ }
+ },
+
+ _initSource: function() {
+ var self = this,
+ array,
+ url;
+ 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 ( self.xhr ) {
+ self.xhr.abort();
+ }
+ self.xhr = $.ajax({
+ url: url,
+ data: request,
+ dataType: "json",
+ autocompleteRequest: ++requestIndex,
+ success: function( data, status ) {
+ if ( this.autocompleteRequest === requestIndex ) {
+ response( data );
+ }
+ },
+ error: function() {
+ if ( this.autocompleteRequest === requestIndex ) {
+ response( [] );
+ }
+ }
+ });
+ };
+ } else {
+ this.source = this.options.source;
+ }
+ },
+
+ search: function( value, event ) {
+ value = value != null ? value : this.element.val();
+
+// console.log(['search', value, event && event.type]);
+ // always save the actual value, not the one passed as an argument
+ this.term = this.element.val();
+
+ if ( value.length < this.options.minLength ) {
+ return this.close( event );
+ }
+
+ clearTimeout( this.closing );
+ if ( this._trigger( "search", event ) === false ) {
+ return;
+ }
+
+ return this._search( value );
+ },
+
+ _search: function( value ) {
+ this.pending++;
+ this.element.addClass( "ui-autocomplete-loading" );
+
+ this.source( { term: value }, this.response );
+ },
+
+ _response: function( content ) {
+ if ( !this.options.disabled && content && content.length ) {
+ content = this._normalize( content );
+ this._suggest( content );
+ this._trigger( "open" );
+ } else {
+ this.close();
+ }
+ this.pending--;
+ if ( !this.pending ) {
+ this.element.removeClass( "ui-autocomplete-loading" );
+ }
+ },
+
+ close: function( event ) {
+ clearTimeout( this.closing );
+ if ( this.menu.element.is(":visible") ) {
+ this.menu.element.hide();
+ this.menu.deactivate();
+ this._trigger( "close", event );
+ }
+ },
+
+ _change: function( event ) {
+ if ( this.previous !== this.element.val() ) {
+ 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()
+ .zIndex( this.element.zIndex() + 1 );
+ this._renderMenu( ul, items );
+ // TODO refresh should check if the active item is still in the dom, removing the need
for a manual deactivate
+ this.menu.deactivate();
+ 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( new $.Event("mouseover") );
+ }
+ },
+
+ _resizeMenu: function() {
+ var ul = this.menu.element;
+ ul.outerWidth( Math.max(
+ ul.width( "" ).outerWidth(),
+ this.element.outerWidth()
+ ) );
+ },
+
+ _renderMenu: function( ul, items ) {
+ var self = this;
+ $.each( items, function( index, item ) {
+ self._renderItem( ul, item );
+ });
+ },
+
+ _renderItem: function( ul, item) {
+ return $( "<li></li>" )
+ .data( "item.autocomplete", item )
+ .append( $( "<a></a>" ).text( item.label ) )
+ .appendTo( ul );
+ },
+
+ _move: function( direction, event ) {
+ if ( !this.menu.element.is(":visible") ) {
+ this.search( null, event );
+ return;
+ }
+ if ( this.menu.first() && /^previous/.test(direction) ||
+ this.menu.last() && /^next/.test(direction) ) {
+ this.element.val( this.term );
+ this.menu.deactivate();
+ return;
+ }
+ this.menu[ direction ]( event );
+ },
+
+ widget: function() {
+ return this.menu.element;
+ }
+});
+
+$.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 );
+ });
+ }
+});
+
+}( jQuery ));
+
+/*
+ * jQuery UI Menu (not officially released)
+ *
+ * This widget isn't yet finished and the API is subject to change. We plan to
finish
+ * it for the next release. You're welcome to give it a try anyway and give us
feedback,
+ * as long as you're okay with migrating your code later on. We can help with that,
too.
+ *
+ * Copyright 2010, AUTHORS.txt (
http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
http://jquery.org/license
+ *
+ *
http://docs.jquery.com/UI/Menu
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function($) {
+
+$.widget("ui.menu", {
+ _create: function() {
+ var self = this;
+ this.element
+ .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
+ .attr({
+ role: "listbox",
+ "aria-activedescendant": "ui-active-menuitem"
+ })
+ .click(function( event ) {
+ if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
+ return;
+ }
+ // temporary
+ event.preventDefault();
+ self.select( event );
+ });
+ this.refresh();
+ },
+
+ refresh: function() {
+ var self = this;
+
+ // don't refresh list items that are already adapted
+ var items = this.element.children("li:not(.ui-menu-item):has(a)")
+ .addClass("ui-menu-item")
+ .attr("role", "menuitem");
+
+ items.children("a")
+ .addClass("ui-corner-all")
+ .attr("tabindex", -1)
+ // mouseenter doesn't work with event delegation
+ .mouseenter(function( event ) {
+ self.activate( event, $(this).parent() );
+ })
+ .mouseleave(function() {
+ self.deactivate();
+ });
+ },
+
+ activate: function( event, item ) {
+ this.deactivate();
+ if (this.hasScroll()) {
+ var offset = item.offset().top - this.element.offset().top,
+ scroll = this.element.scrollTop(),
+ elementHeight = this.element.height();
+ if (offset < 0) {
+ this.element.scrollTop( scroll + offset);
+ } else if (offset >= elementHeight) {
+ this.element.scrollTop( scroll + offset - elementHeight + item.height());
+ }
+ }
+ this.active = item.eq(0)
+ .children("a")
+ .addClass("ui-state-hover")
+ .attr("id", "ui-active-menuitem")
+ .end();
+ this._trigger("focus", event, { item: item });
+ },
+
+ deactivate: function() {
+ if (!this.active) { return; }
+
+ this.active.children("a")
+ .removeClass("ui-state-hover")
+ .removeAttr("id");
+ this._trigger("blur");
+ this.active = null;
+ },
+
+ next: function(event) {
+ this.move("next", ".ui-menu-item:first", event);
+ },
+
+ previous: function(event) {
+ this.move("prev", ".ui-menu-item:last", event);
+ },
+
+ first: function() {
+ return this.active && !this.active.prevAll(".ui-menu-item").length;
+ },
+
+ last: function() {
+ return this.active && !this.active.nextAll(".ui-menu-item").length;
+ },
+
+ move: function(direction, edge, event) {
+ if (!this.active) {
+ this.activate(event, this.element.children(edge));
+ return;
+ }
+ var next = this.active[direction + "All"](".ui-menu-item").eq(0);
+ if (next.length) {
+ this.activate(event, next);
+ } else {
+ this.activate(event, this.element.children(edge));
+ }
+ },
+
+ // TODO merge with previousPage
+ nextPage: function(event) {
+ if (this.hasScroll()) {
+ // TODO merge with no-scroll-else
+ if (!this.active || this.last()) {
+ this.activate(event, this.element.children(".ui-menu-item:first"));
+ return;
+ }
+ var base = this.active.offset().top,
+ height = this.element.height(),
+ result = this.element.children(".ui-menu-item").filter(function() {
+ var close = $(this).offset().top - base - height + $(this).height();
+ // TODO improve approximation
+ return close < 10 && close > -10;
+ });
+
+ // TODO try to catch this earlier when scrollTop indicates the last page anyway
+ if (!result.length) {
+ result = this.element.children(".ui-menu-item:last");
+ }
+ this.activate(event, result);
+ } else {
+ this.activate(event, this.element.children(".ui-menu-item")
+ .filter(!this.active || this.last() ? ":first" : ":last"));
+ }
+ },
+
+ // TODO merge with nextPage
+ previousPage: function(event) {
+ if (this.hasScroll()) {
+ // TODO merge with no-scroll-else
+ if (!this.active || this.first()) {
+ this.activate(event, this.element.children(".ui-menu-item:last"));
+ return;
+ }
+
+ var base = this.active.offset().top,
+ height = this.element.height();
+ result = this.element.children(".ui-menu-item").filter(function() {
+ var close = $(this).offset().top - base + height - $(this).height();
+ // TODO improve approximation
+ return close < 10 && close > -10;
+ });
+
+ // TODO try to catch this earlier when scrollTop indicates the last page anyway
+ if (!result.length) {
+ result = this.element.children(".ui-menu-item:first");
+ }
+ this.activate(event, result);
+ } else {
+ this.activate(event, this.element.children(".ui-menu-item")
+ .filter(!this.active || this.first() ? ":last" : ":first"));
+ }
+ },
+
+ hasScroll: function() {
+ return this.element.height() < this.element[ $.fn.prop ? "prop" :
"attr" ]("scrollHeight");
+ },
+
+ select: function( event ) {
+ this._trigger("selected", event, { item: this.active });
+ }
+});
+
+}(jQuery));
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.core.js
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.core.js
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.core.js 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,312 @@
+/*!
+ * jQuery UI 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (
http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
http://jquery.org/license
+ *
+ *
http://docs.jquery.com/UI
+ */
+(function( $, undefined ) {
+
+// prevent duplicate loading
+// this is only a problem because we proxy existing functions
+// and we don't want to double proxy them
+$.ui = $.ui || {};
+if ( $.ui.version ) {
+ return;
+}
+
+$.extend( $.ui, {
+ version: "1.8.13",
+
+ keyCode: {
+ ALT: 18,
+ BACKSPACE: 8,
+ CAPS_LOCK: 20,
+ COMMA: 188,
+ COMMAND: 91,
+ COMMAND_LEFT: 91, // COMMAND
+ COMMAND_RIGHT: 93,
+ CONTROL: 17,
+ DELETE: 46,
+ DOWN: 40,
+ END: 35,
+ ENTER: 13,
+ ESCAPE: 27,
+ HOME: 36,
+ INSERT: 45,
+ LEFT: 37,
+ MENU: 93, // COMMAND_RIGHT
+ 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,
+ SHIFT: 16,
+ SPACE: 32,
+ TAB: 9,
+ UP: 38,
+ WINDOWS: 91 // COMMAND
+ }
+});
+
+// plugins
+$.fn.extend({
+ _focus: $.fn.focus,
+ focus: function( delay, fn ) {
+ return typeof delay === "number" ?
+ this.each(function() {
+ var elem = this;
+ setTimeout(function() {
+ $( elem ).focus();
+ if ( fn ) {
+ fn.call( elem );
+ }
+ }, delay );
+ }) :
+ this._focus.apply( this, arguments );
+ },
+
+ scrollParent: function() {
+ var scrollParent;
+ if (($.browser.msie &&
(/(static|relative)/).test(this.css('position'))) ||
(/absolute/).test(this.css('position'))) {
+ scrollParent = this.parents().filter(function() {
+ return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1))
&&
(/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
+ }).eq(0);
+ } else {
+ scrollParent = this.parents().filter(function() {
+ return
(/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1));
+ }).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;
+ },
+
+ disableSelection: function() {
+ return this.bind( ( $.support.selectstart ? "selectstart" :
"mousedown" ) +
+ ".ui-disableSelection", function( event ) {
+ event.preventDefault();
+ });
+ },
+
+ enableSelection: function() {
+ return this.unbind( ".ui-disableSelection" );
+ }
+});
+
+$.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( $.curCSS( elem, "padding" + this, true) ) || 0;
+ if ( border ) {
+ size -= parseFloat( $.curCSS( elem, "border" + this + "Width",
true) ) || 0;
+ }
+ if ( margin ) {
+ size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 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" );
+ });
+ };
+});
+
+// selectors
+function focusable( element, isTabIndexNotNaN ) {
+ var nodeName = element.nodeName.toLowerCase();
+ if ( "area" === nodeName ) {
+ var map = element.parentNode,
+ mapName = map.name,
+ img;
+ 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 !$( element ).parents().andSelf().filter(function() {
+ return $.curCSS( this, "visibility" ) === "hidden" ||
+ $.expr.filters.hidden( this );
+ }).length;
+}
+
+$.extend( $.expr[ ":" ], {
+ data: 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
+$(function() {
+ var body = document.body,
+ div = body.appendChild( div = document.createElement( "div" ) );
+
+ $.extend( div.style, {
+ minHeight: "100px",
+ height: "auto",
+ padding: 0,
+ borderWidth: 0
+ });
+
+ $.support.minHeight = div.offsetHeight === 100;
+ $.support.selectstart = "onselectstart" in div;
+
+ // set display to none to avoid a layout bug in IE
+ //
http://dev.jquery.com/ticket/4014
+ body.removeChild( div ).style.display = "none";
+});
+
+
+
+
+
+// deprecated
+$.extend( $.ui, {
+ // $.ui.plugin is deprecated. Use the proxy pattern instead.
+ plugin: {
+ add: function( module, option, set ) {
+ var proto = $.ui[ module ].prototype;
+ for ( var i in set ) {
+ proto.plugins[ i ] = proto.plugins[ i ] || [];
+ proto.plugins[ i ].push( [ option, set[ i ] ] );
+ }
+ },
+ call: function( instance, name, args ) {
+ var set = instance.plugins[ name ];
+ if ( !set || !instance.element[ 0 ].parentNode ) {
+ return;
+ }
+
+ for ( var i = 0; i < set.length; i++ ) {
+ if ( instance.options[ set[ i ][ 0 ] ] ) {
+ set[ i ][ 1 ].apply( instance.element, args );
+ }
+ }
+ }
+ },
+
+ // will be deprecated when we switch to jQuery 1.4 - use jQuery.contains()
+ contains: function( a, b ) {
+ return document.compareDocumentPosition ?
+ a.compareDocumentPosition( b ) & 16 :
+ a !== b && a.contains( b );
+ },
+
+ // 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;
+ },
+
+ // these are odd functions, fix the API or move into individual plugins
+ isOverAxis: function( x, reference, size ) {
+ //Determines when x coordinate is over "b" element axis
+ return ( x > reference ) && ( x < ( reference + size ) );
+ },
+ isOver: function( y, x, top, left, height, width ) {
+ //Determines when x, y coordinates is over "b" element
+ return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
+ }
+});
+
+})( jQuery );
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.position.js
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.position.js
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.position.js 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,252 @@
+/*
+ * jQuery UI Position 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (
http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
http://jquery.org/license
+ *
+ *
http://docs.jquery.com/UI/Position
+ */
+(function( $, undefined ) {
+
+$.ui = $.ui || {};
+
+var horizontalPositions = /left|center|right/,
+ verticalPositions = /top|center|bottom/,
+ center = "center",
+ _position = $.fn.position,
+ _offset = $.fn.offset;
+
+$.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 target = $( options.of ),
+ targetElem = target[0],
+ collision = ( options.collision || "flip" ).split( " " ),
+ offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ],
+ targetWidth,
+ targetHeight,
+ basePosition;
+
+ if ( targetElem.nodeType === 9 ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ basePosition = { top: 0, left: 0 };
+ // TODO: use $.isWindow() in 1.9
+ } else if ( targetElem.setTimeout ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ basePosition = { top: target.scrollTop(), left: target.scrollLeft() };
+ } else if ( targetElem.preventDefault ) {
+ // force left top to allow flipping
+ options.at = "left top";
+ targetWidth = targetHeight = 0;
+ basePosition = { top: options.of.pageY, left: options.of.pageX };
+ } else {
+ targetWidth = target.outerWidth();
+ targetHeight = target.outerHeight();
+ basePosition = target.offset();
+ }
+
+ // force my and at to have valid horizontal and veritcal positions
+ // if a value is missing or invalid, it will be converted to center
+ $.each( [ "my", "at" ], function() {
+ var pos = ( options[this] || "" ).split( " " );
+ if ( pos.length === 1) {
+ pos = horizontalPositions.test( pos[0] ) ?
+ pos.concat( [center] ) :
+ verticalPositions.test( pos[0] ) ?
+ [ center ].concat( pos ) :
+ [ center, center ];
+ }
+ pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center;
+ pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center;
+ options[ this ] = pos;
+ });
+
+ // normalize collision option
+ if ( collision.length === 1 ) {
+ collision[ 1 ] = collision[ 0 ];
+ }
+
+ // normalize offset option
+ offset[ 0 ] = parseInt( offset[0], 10 ) || 0;
+ if ( offset.length === 1 ) {
+ offset[ 1 ] = offset[ 0 ];
+ }
+ offset[ 1 ] = parseInt( offset[1], 10 ) || 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;
+ }
+
+ basePosition.left += offset[ 0 ];
+ basePosition.top += offset[ 1 ];
+
+ return this.each(function() {
+ var elem = $( this ),
+ elemWidth = elem.outerWidth(),
+ elemHeight = elem.outerHeight(),
+ marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0,
+ marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0,
+ collisionWidth = elemWidth + marginLeft +
+ ( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ),
+ collisionHeight = elemHeight + marginTop +
+ ( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ),
+ position = $.extend( {}, basePosition ),
+ collisionPosition;
+
+ 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;
+ }
+
+ // prevent fractions (see #5280)
+ position.left = Math.round( position.left );
+ position.top = Math.round( position.top );
+
+ collisionPosition = {
+ left: position.left - marginLeft,
+ top: position.top - 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: offset,
+ my: options.my,
+ at: options.at
+ });
+ }
+ });
+
+ if ( $.fn.bgiframe ) {
+ elem.bgiframe();
+ }
+ elem.offset( $.extend( position, { using: options.using } ) );
+ });
+};
+
+$.ui.position = {
+ fit: {
+ left: function( position, data ) {
+ var win = $( window ),
+ over = data.collisionPosition.left + data.collisionWidth - win.width() -
win.scrollLeft();
+ position.left = over > 0 ? position.left - over : Math.max( position.left -
data.collisionPosition.left, position.left );
+ },
+ top: function( position, data ) {
+ var win = $( window ),
+ over = data.collisionPosition.top + data.collisionHeight - win.height() -
win.scrollTop();
+ position.top = over > 0 ? position.top - over : Math.max( position.top -
data.collisionPosition.top, position.top );
+ }
+ },
+
+ flip: {
+ left: function( position, data ) {
+ if ( data.at[0] === center ) {
+ return;
+ }
+ var win = $( window ),
+ over = data.collisionPosition.left + data.collisionWidth - win.width() -
win.scrollLeft(),
+ myOffset = data.my[ 0 ] === "left" ?
+ -data.elemWidth :
+ data.my[ 0 ] === "right" ?
+ data.elemWidth :
+ 0,
+ atOffset = data.at[ 0 ] === "left" ?
+ data.targetWidth :
+ -data.targetWidth,
+ offset = -2 * data.offset[ 0 ];
+ position.left += data.collisionPosition.left < 0 ?
+ myOffset + atOffset + offset :
+ over > 0 ?
+ myOffset + atOffset + offset :
+ 0;
+ },
+ top: function( position, data ) {
+ if ( data.at[1] === center ) {
+ return;
+ }
+ var win = $( window ),
+ over = data.collisionPosition.top + data.collisionHeight - win.height() -
win.scrollTop(),
+ myOffset = data.my[ 1 ] === "top" ?
+ -data.elemHeight :
+ data.my[ 1 ] === "bottom" ?
+ data.elemHeight :
+ 0,
+ atOffset = data.at[ 1 ] === "top" ?
+ data.targetHeight :
+ -data.targetHeight,
+ offset = -2 * data.offset[ 1 ];
+ position.top += data.collisionPosition.top < 0 ?
+ myOffset + atOffset + offset :
+ over > 0 ?
+ myOffset + atOffset + offset :
+ 0;
+ }
+ }
+};
+
+// offset setter from jQuery 1.4
+if ( !$.offset.setOffset ) {
+ $.offset.setOffset = function( elem, options ) {
+ // set position first, in-case top/left are set even on static elem
+ if ( /static/.test( $.curCSS( elem, "position" ) ) ) {
+ elem.style.position = "relative";
+ }
+ var curElem = $( elem ),
+ curOffset = curElem.offset(),
+ curTop = parseInt( $.curCSS( elem, "top", true ), 10 ) || 0,
+ curLeft = parseInt( $.curCSS( elem, "left", true ), 10) || 0,
+ props = {
+ top: (options.top - curOffset.top) + curTop,
+ left: (options.left - curOffset.left) + curLeft
+ };
+
+ if ( 'using' in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ };
+
+ $.fn.offset = function( options ) {
+ var elem = this[ 0 ];
+ if ( !elem || !elem.ownerDocument ) { return null; }
+ if ( options ) {
+ return this.each(function() {
+ $.offset.setOffset( this, options );
+ });
+ }
+ return _offset.call( this );
+ };
+}
+
+}( jQuery ));
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.widget.js
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.widget.js
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/jquery.ui.widget.js 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,262 @@
+/*!
+ * jQuery UI Widget 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (
http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
http://jquery.org/license
+ *
+ *
http://docs.jquery.com/UI/Widget
+ */
+(function( $, undefined ) {
+
+// jQuery 1.4+
+if ( $.cleanData ) {
+ var _cleanData = $.cleanData;
+ $.cleanData = function( elems ) {
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ $( elem ).triggerHandler( "remove" );
+ }
+ _cleanData( elems );
+ };
+} else {
+ var _remove = $.fn.remove;
+ $.fn.remove = function( selector, keepData ) {
+ return this.each(function() {
+ if ( !keepData ) {
+ if ( !selector || $.filter( selector, [ this ] ).length ) {
+ $( "*", this ).add( [ this ] ).each(function() {
+ $( this ).triggerHandler( "remove" );
+ });
+ }
+ }
+ return _remove.call( $(this), selector, keepData );
+ });
+ };
+}
+
+$.widget = function( name, base, prototype ) {
+ var namespace = name.split( "." )[ 0 ],
+ fullName;
+ name = name.split( "." )[ 1 ];
+ fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ // create selector for plugin
+ $.expr[ ":" ][ fullName ] = function( elem ) {
+ return !!$.data( elem, name );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ $[ namespace ][ name ] = function( options, element ) {
+ // allow instantiation without initializing for simple inheritance
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+
+ var 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
+// $.each( basePrototype, function( key, val ) {
+// if ( $.isPlainObject(val) ) {
+// basePrototype[ key ] = $.extend( {}, val );
+// }
+// });
+ basePrototype.options = $.extend( true, {}, basePrototype.options );
+ $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
+ namespace: namespace,
+ widgetName: name,
+ widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
+ widgetBaseClass: fullName
+ }, prototype );
+
+ $.widget.bridge( name, $[ namespace ][ name ] );
+};
+
+$.widget.bridge = function( name, object ) {
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string",
+ args = Array.prototype.slice.call( arguments, 1 ),
+ returnValue = this;
+
+ // allow multiple hashes to be passed on init
+ options = !isMethodCall && args.length ?
+ $.extend.apply( null, [ true, options ].concat(args) ) :
+ options;
+
+ // prevent calls to internal methods
+ if ( isMethodCall && options.charAt( 0 ) === "_" ) {
+ return returnValue;
+ }
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var instance = $.data( this, name ),
+ methodValue = instance && $.isFunction( instance[options] ) ?
+ instance[ options ].apply( instance, args ) :
+ instance;
+ // TODO: add this back in 1.9 and use $.error() (see #5972)
+// if ( !instance ) {
+// throw "cannot call methods on " + name + " prior to initialization;
" +
+// "attempted to call method '" + options + "'";
+// }
+// if ( !$.isFunction( instance[options] ) ) {
+// throw "no such method '" + options + "' for " + name +
" widget instance";
+// }
+// var methodValue = instance[ options ].apply( instance, args );
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue;
+ return false;
+ }
+ });
+ } else {
+ this.each(function() {
+ var instance = $.data( this, name );
+ if ( instance ) {
+ instance.option( options || {} )._init();
+ } else {
+ $.data( this, name, new object( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+};
+
+$.Widget = function( options, element ) {
+ // allow instantiation without initializing for simple inheritance
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+};
+
+$.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ options: {
+ disabled: false
+ },
+ _createWidget: function( options, element ) {
+ // $.widget.bridge stores the plugin instance, but we do it anyway
+ // so that it's stored even before the _create function runs
+ $.data( element, this.widgetName, this );
+ this.element = $( element );
+ this.options = $.extend( true, {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ var self = this;
+ this.element.bind( "remove." + this.widgetName, function() {
+ self.destroy();
+ });
+
+ this._create();
+ this._trigger( "create" );
+ this._init();
+ },
+ _getCreateOptions: function() {
+ return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
+ },
+ _create: function() {},
+ _init: function() {},
+
+ destroy: function() {
+ this.element
+ .unbind( "." + this.widgetName )
+ .removeData( this.widgetName );
+ this.widget()
+ .unbind( "." + this.widgetName )
+ .removeAttr( "aria-disabled" )
+ .removeClass(
+ this.widgetBaseClass + "-disabled " +
+ "ui-state-disabled" );
+ },
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key;
+
+ if ( arguments.length === 0 ) {
+ // don't return a reference to the internal hash
+ return $.extend( {}, this.options );
+ }
+
+ if (typeof key === "string" ) {
+ if ( value === undefined ) {
+ return this.options[ key ];
+ }
+ options = {};
+ options[ key ] = value;
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+ _setOptions: function( options ) {
+ var self = this;
+ $.each( options, function( key, value ) {
+ self._setOption( key, value );
+ });
+
+ return this;
+ },
+ _setOption: function( key, value ) {
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this.widget()
+ [ value ? "addClass" : "removeClass"](
+ this.widgetBaseClass + "-disabled" + " " +
+ "ui-state-disabled" )
+ .attr( "aria-disabled", value );
+ }
+
+ return this;
+ },
+
+ enable: function() {
+ return this._setOption( "disabled", false );
+ },
+ disable: function() {
+ return this._setOption( "disabled", true );
+ },
+
+ _trigger: function( type, event, data ) {
+ var callback = this.options[ type ];
+
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+ data = data || {};
+
+ // copy original event properties over to the new event
+ // this would happen if we could call $.event.fix instead of $.Event
+ // but we don't have a way to force an event to be fixed multiple times
+ if ( event.originalEvent ) {
+ for ( var i = $.event.props.length, prop; i; ) {
+ prop = $.event.props[ --i ];
+ event[ prop ] = event.originalEvent[ prop ];
+ }
+ }
+
+ this.element.trigger( event, data );
+
+ return !( $.isFunction(callback) &&
+ callback.call( this.element[0], event, data ) === false ||
+ event.isDefaultPrevented() );
+ }
+};
+
+})( jQuery );
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/richfaces.visualsearch.js
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/richfaces.visualsearch.js
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/richfaces.visualsearch.js 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,179 @@
+/**
+ * This function evaluates code in template with object in ScopeChain.
+ * This is usefull if you need to evaluate code that uses member names
+ * that colide with external names that the code refers to.
+ * There is almost exact method in utils.js called Richfaces.eval,
+ * but it swallows exception thrown during evaluation, which makes debugging
+ * hard.
+ */
+(function ($, rf)
+{
+ // Create (for example) ui container for our component class
+ rf.ui = rf.ui || {};
+ // Default options definition if needed for the component
+ // var defaultOptions = {};
+ var SUBMIT_EVENT_FUNCTION = 'submitEventFunction';
+ // Extending component class with new properties and methods using extendClass
+ // $super - reference to the parent prototype, will be available inside those
methods
+ rf.ui.Visualsearch = rf.BaseComponent.extendClass({
+ // class name
+ name:"Visualsearch",
+ init: function (componentId, options)
+ {
+ if (!document.getElementById(componentId)) {
+ throw "No element with id '" + componentId + "'
found.";
+ }
+ this.options = options;
+ // call constructor of parent class if needed
+ $super.constructor.call(this, componentId);
+ // attach component object to DOM element for
+ // future cleaning and for client side API calls
+ this.attachToDom(this.id);
+ // ...
+ /**
+ * Message bundle & event handlers setup.
+ */
+ options = jQuery.extend({
+ container : jQuery(document.getElementById(this.id)),
+ query : '',
+ unquotable: ["state"],
+ callbacks : {
+ search: function(query, searchCollection)
+ {
+ _this.__search(query, searchCollection)
+ },
+ valueMatches: function(facet, searchTerm, callback)
+ {
+ _this.__valueMatches(facet, searchTerm, callback)
+ },
+ facetMatches: function(searchTerm, callback)
+ {
+ _this.__facetMatches(searchTerm, callback)
+ }
+ }
+ }, options);
+ var _this = this;
+ jQuery(function()
+ {
+ _this.delegate = VS.init(options);
+ });
+ },
+ // private functions definition
+ /**
+ * Utility functions.
+ */
+ __getResponseComponentData : function(event)
+ {
+ return event.componentData[this.id];
+ },
+ /**
+ * Executes event handler passed in options.
+ * @param eventName name of the event
+ * @param context hash of variables that should be placed in scope when during
evaluation
+ */
+ __executeInlineEventHandler : function(eventName, context)
+ {
+ if (this.options[eventName] != null) {
+ return rf.ui.Visualsearch.eval("(function(){" +
this.options[eventName] + "})()", context);
+ } else {
+ return null;
+ }
+ },
+ __getDelegate : function()
+ {
+ return this.delegate;
+ },
+ __valueMatches : function (facet, searchTerm, callback)
+ {
+ if(this.options.values !=null) {
+ callback(this.options.values[facet]);
+ return;
+ }
+ if (this.options[SUBMIT_EVENT_FUNCTION] != null) {
+ var _this = this;
+ this.options[SUBMIT_EVENT_FUNCTION]({} /* stub event */,
'suggestValue', null, null, facet, searchTerm, function(event)
+ {
+ var data = _this.__getResponseComponentData(event);
+ if (data != undefined) {
+ callback(data);
+ }
+ });
+ }
+ },
+ __facetMatches : function (searchTerm, callback)
+ {
+ if(this.options.facets !=null) {
+ callback(this.options.facets);
+ return;
+ }
+ if (this.options[SUBMIT_EVENT_FUNCTION] != null) {
+ var _this = this;
+ this.options[SUBMIT_EVENT_FUNCTION]({} /* stub event */,
'suggestFacet', null, null, null, searchTerm, function(event)
+ {
+ var data = _this.__getResponseComponentData(event);
+ if (data != undefined) {
+ callback(data);
+ }
+ });
+ }
+ },
+ __search : function(query, searchCollection)
+ {
+ var queryJSON = {};
+ searchCollection.each(function(model)
+ {
+ var attributeName = model.get("category");
+ //noinspection UnnecessaryLocalVariableJS
+ var attributeValue = model.get("value");
+ queryJSON[attributeName] = attributeValue;
+ });
+ if (this.options[SUBMIT_EVENT_FUNCTION] != null) {
+ var _this = this;
+ document.getElementById(this.id).value=query;
+ this.options[SUBMIT_EVENT_FUNCTION]({} /* stub event */,
'search', query, JSON.stringify(queryJSON), null, null, function(event)
+ {
+ var data = _this.__getResponseComponentData(event);
+ if (data != undefined) {
+ callback(data);
+ }
+ _this.__executeInlineEventHandler('onsearch', {
'query':query, 'queryJSON':queryJSON});
+ });
+ } else {
+ this.__executeInlineEventHandler('onsearch', {
'query':query, 'queryJSON':queryJSON});
+ }
+ }
+ ,
+ // destructor definition
+ destroy: function ()
+ {
+ // define destructor if additional cleaning is needed but
+ // in most cases its not nessesary.
+ // call parent’s destructor
+ $super.destroy.call(this);
+ }
+ });
+ // define super class reference - reference to the parent prototype
+ var $super = rf.ui.Visualsearch.$super;
+})(jQuery, RichFaces);
+RichFaces.ui.Visualsearch.eval = function(template, object)
+{
+ var value;
+ with (object) {
+ value = eval(template);
+ }
+ return value;
+};
+
+/**
+ * Add :focus selector to jQuery since it is in 1.6 and RF is shipped with 1.5
+ */
+(function ($)
+{
+ var filters = $.expr[":"];
+ if (!filters.focus) {
+ filters.focus = function(elem)
+ {
+ return elem === document.activeElement && ( elem.type || elem.href
);
+ };
+ }
+})(jQuery);
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/underscore.js
===================================================================
--- sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/underscore.js
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/underscore.js 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,796 @@
+// Underscore.js 1.1.5
+// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the MIT license.
+// Portions of Underscore are inspired or borrowed from Prototype,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+//
http://documentcloud.github.com/underscore
+
+(function() {
+
+ // Baseline setup
+ // --------------
+
+ // Establish the root object, `window` in the browser, or `global` on the server.
+ var root = this;
+
+ // Save the previous value of the `_` variable.
+ var previousUnderscore = root._;
+
+ // Establish the object that gets returned to break out of a loop iteration.
+ var breaker = {};
+
+ // Save bytes in the minified (but not gzipped) version:
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto =
Function.prototype;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var slice = ArrayProto.slice,
+ unshift = ArrayProto.unshift,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
+
+ // All **ECMAScript 5** native function implementations that we hope to use
+ // are declared here.
+ var
+ nativeForEach = ArrayProto.forEach,
+ nativeMap = ArrayProto.map,
+ nativeReduce = ArrayProto.reduce,
+ nativeReduceRight = ArrayProto.reduceRight,
+ nativeFilter = ArrayProto.filter,
+ nativeEvery = ArrayProto.every,
+ nativeSome = ArrayProto.some,
+ nativeIndexOf = ArrayProto.indexOf,
+ nativeLastIndexOf = ArrayProto.lastIndexOf,
+ nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys,
+ nativeBind = FuncProto.bind;
+
+ // Create a safe reference to the Underscore object for use below.
+ var _ = function(obj) { return new wrapper(obj); };
+
+ // Export the Underscore object for **CommonJS**, with backwards-compatibility
+ // for the old `require()` API. If we're not in CommonJS, add `_` to the
+ // global object.
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = _;
+ _._ = _;
+ } else {
+ root._ = _;
+ }
+
+ // Current version.
+ _.VERSION = '1.1.5';
+
+ // Collection Functions
+ // --------------------
+
+ // The cornerstone, an `each` implementation, aka `forEach`.
+ // Handles objects implementing `forEach`, arrays, and raw objects.
+ // Delegates to **ECMAScript 5**'s native `forEach` if available.
+ var each = _.each = _.forEach = function(obj, iterator, context) {
+ if (obj == null) return;
+ if (nativeForEach && obj.forEach === nativeForEach) {
+ obj.forEach(iterator, context);
+ } else if (_.isNumber(obj.length)) {
+ for (var i = 0, l = obj.length; i < l; i++) {
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
+ }
+ } else {
+ for (var key in obj) {
+ if (hasOwnProperty.call(obj, key)) {
+ if (iterator.call(context, obj[key], key, obj) === breaker) return;
+ }
+ }
+ }
+ };
+
+ // Return the results of applying the iterator to each element.
+ // Delegates to **ECMAScript 5**'s native `map` if available.
+ _.map = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+ each(obj, function(value, index, list) {
+ results[results.length] = iterator.call(context, value, index, list);
+ });
+ return results;
+ };
+
+ // **Reduce** builds up a single result from a list of values, aka `inject`,
+ // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
+ _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
+ var initial = memo !== void 0;
+ if (obj == null) obj = [];
+ if (nativeReduce && obj.reduce === nativeReduce) {
+ if (context) iterator = _.bind(iterator, context);
+ return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+ }
+ each(obj, function(value, index, list) {
+ if (!initial && index === 0) {
+ memo = value;
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, value, index, list);
+ }
+ });
+ if (!initial) throw new TypeError("Reduce of empty array with no initial
value");
+ return memo;
+ };
+
+ // The right-associative version of reduce, also known as `foldr`.
+ // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
+ _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+ if (obj == null) obj = [];
+ if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
+ if (context) iterator = _.bind(iterator, context);
+ return memo !== void 0 ? obj.reduceRight(iterator, memo) :
obj.reduceRight(iterator);
+ }
+ var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
+ return _.reduce(reversed, iterator, memo, context);
+ };
+
+ // Return the first value which passes a truth test. Aliased as `detect`.
+ _.find = _.detect = function(obj, iterator, context) {
+ var result;
+ any(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) {
+ result = value;
+ return true;
+ }
+ });
+ return result;
+ };
+
+ // Return all the elements that pass a truth test.
+ // Delegates to **ECMAScript 5**'s native `filter` if available.
+ // Aliased as `select`.
+ _.filter = _.select = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator,
context);
+ each(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) results[results.length] = value;
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ each(obj, function(value, index, list) {
+ if (!iterator.call(context, value, index, list)) results[results.length] = value;
+ });
+ return results;
+ };
+
+ // Determine whether all of the elements match a truth test.
+ // Delegates to **ECMAScript 5**'s native `every` if available.
+ // Aliased as `all`.
+ _.every = _.all = function(obj, iterator, context) {
+ iterator = iterator || _.identity;
+ var result = true;
+ if (obj == null) return result;
+ if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator,
context);
+ each(obj, function(value, index, list) {
+ if (!(result = result && iterator.call(context, value, index, list)))
return breaker;
+ });
+ return result;
+ };
+
+ // Determine if at least one element in the object matches a truth test.
+ // Delegates to **ECMAScript 5**'s native `some` if available.
+ // Aliased as `any`.
+ var any = _.some = _.any = function(obj, iterator, context) {
+ iterator = iterator || _.identity;
+ var result = false;
+ if (obj == null) return result;
+ if (nativeSome && obj.some === nativeSome) return obj.some(iterator,
context);
+ each(obj, function(value, index, list) {
+ if (result = iterator.call(context, value, index, list)) return breaker;
+ });
+ return result;
+ };
+
+ // Determine if a given value is included in the array or object using `===`.
+ // Aliased as `contains`.
+ _.include = _.contains = function(obj, target) {
+ var found = false;
+ if (obj == null) return found;
+ if (nativeIndexOf && obj.indexOf === nativeIndexOf) return
obj.indexOf(target) != -1;
+ any(obj, function(value) {
+ if (found = value === target) return true;
+ });
+ return found;
+ };
+
+ // Invoke a method (with arguments) on every item in a collection.
+ _.invoke = function(obj, method) {
+ var args = slice.call(arguments, 2);
+ return _.map(obj, function(value) {
+ return (method ? value[method] : value).apply(value, args);
+ });
+ };
+
+ // Convenience version of a common use case of `map`: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, function(value){ return value[key]; });
+ };
+
+ // Return the maximum element or (element-based computation).
+ _.max = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
+ var result = {computed : -Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed >= result.computed && (result = {value : value, computed :
computed});
+ });
+ return result.value;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
+ var result = {computed : Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed < result.computed && (result = {value : value, computed :
computed});
+ });
+ return result.value;
+ };
+
+ // Sort the object's values by a criterion produced by an iterator.
+ _.sortBy = function(obj, iterator, context) {
+ return _.pluck(_.map(obj, function(value, index, list) {
+ return {
+ value : value,
+ criteria : iterator.call(context, value, index, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }), 'value');
+ };
+
+ // Use a comparator function to figure out at what index an object should
+ // be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iterator) {
+ iterator = iterator || _.identity;
+ var low = 0, high = array.length;
+ while (low < high) {
+ var mid = (low + high) >> 1;
+ iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
+ }
+ return low;
+ };
+
+ // Safely convert anything iterable into a real, live array.
+ _.toArray = function(iterable) {
+ if (!iterable) return [];
+ if (iterable.toArray) return iterable.toArray();
+ if (_.isArray(iterable)) return iterable;
+ if (_.isArguments(iterable)) return slice.call(iterable);
+ return _.values(iterable);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ return _.toArray(obj).length;
+ };
+
+ // Array Functions
+ // ---------------
+
+ // Get the first element of an array. Passing **n** will return the first N
+ // values in the array. Aliased as `head`. The **guard** check allows it to work
+ // with `_.map`.
+ _.first = _.head = function(array, n, guard) {
+ return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
+ };
+
+ // Returns everything but the first entry of the array. Aliased as `tail`.
+ // Especially useful on the arguments object. Passing an **index** will return
+ // the rest of the values in the array from that index onward. The **guard**
+ // check allows it to work with `_.map`.
+ _.rest = _.tail = function(array, index, guard) {
+ return slice.call(array, (index == null) || guard ? 1 : index);
+ };
+
+ // Get the last element of an array.
+ _.last = function(array) {
+ return array[array.length - 1];
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.filter(array, function(value){ return !!value; });
+ };
+
+ // Return a completely flattened version of an array.
+ _.flatten = function(array) {
+ return _.reduce(array, function(memo, value) {
+ if (_.isArray(value)) return memo.concat(_.flatten(value));
+ memo[memo.length] = value;
+ return memo;
+ }, []);
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = function(array) {
+ var values = slice.call(arguments, 1);
+ return _.filter(array, function(value){ return !_.include(values, value); });
+ };
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ // Aliased as `unique`.
+ _.uniq = _.unique = function(array, isSorted) {
+ return _.reduce(array, function(memo, el, i) {
+ if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el)))
memo[memo.length] = el;
+ return memo;
+ }, []);
+ };
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays.
+ _.intersect = function(array) {
+ var rest = slice.call(arguments, 1);
+ return _.filter(_.uniq(array), function(item) {
+ return _.every(rest, function(other) {
+ return _.indexOf(other, item) >= 0;
+ });
+ });
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = function() {
+ var args = slice.call(arguments);
+ var length = _.max(_.pluck(args, 'length'));
+ var results = new Array(length);
+ for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
+ return results;
+ };
+
+ // If the browser doesn't supply us with indexOf (I'm looking at you,
**MSIE**),
+ // we need this function. Return the position of the first occurrence of an
+ // item in an array, or -1 if the item is not included in the array.
+ // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+ // If the array is large and already in sort order, pass `true`
+ // for **isSorted** to use binary search.
+ _.indexOf = function(array, item, isSorted) {
+ if (array == null) return -1;
+ var i, l;
+ if (isSorted) {
+ i = _.sortedIndex(array, item);
+ return array[i] === item ? i : -1;
+ }
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return
array.indexOf(item);
+ for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
+ return -1;
+ };
+
+
+ // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
+ _.lastIndexOf = function(array, item) {
+ if (array == null) return -1;
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return
array.lastIndexOf(item);
+ var i = array.length;
+ while (i--) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python `range()` function. See
+ // [the Python
documentation](http://docs.python.org/library/functions.html#range).
+ _.range = function(start, stop, step) {
+ if (arguments.length <= 1) {
+ stop = start || 0;
+ start = 0;
+ }
+ step = arguments[2] || 1;
+
+ var len = Math.max(Math.ceil((stop - start) / step), 0);
+ var idx = 0;
+ var range = new Array(len);
+
+ while(idx < len) {
+ range[idx++] = start;
+ start += step;
+ }
+
+ return range;
+ };
+
+ // Function (ahem) Functions
+ // ------------------
+
+ // Create a function bound to a given object (assigning `this`, and arguments,
+ // optionally). Binding with arguments is also known as `curry`.
+ // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
+ // We check for `func.bind` first, to fail fast when `func` is undefined.
+ _.bind = function(func, obj) {
+ if (func.bind === nativeBind && nativeBind) return func.bind.apply(func,
slice.call(arguments, 1));
+ var args = slice.call(arguments, 2);
+ return function() {
+ return func.apply(obj, args.concat(slice.call(arguments)));
+ };
+ };
+
+ // Bind all of an object's methods to that object. Useful for ensuring that
+ // all callbacks defined on an object belong to it.
+ _.bindAll = function(obj) {
+ var funcs = slice.call(arguments, 1);
+ if (funcs.length == 0) funcs = _.functions(obj);
+ each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+ return obj;
+ };
+
+ // Memoize an expensive function by storing its results.
+ _.memoize = function(func, hasher) {
+ var memo = {};
+ hasher = hasher || _.identity;
+ return function() {
+ var key = hasher.apply(this, arguments);
+ return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this,
arguments));
+ };
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = function(func, wait) {
+ var args = slice.call(arguments, 2);
+ return setTimeout(function(){ return func.apply(func, args); }, wait);
+ };
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = function(func) {
+ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+ };
+
+ // Internal function used to implement `_.throttle` and `_.debounce`.
+ var limit = function(func, wait, debounce) {
+ var timeout;
+ return function() {
+ var context = this, args = arguments;
+ var throttler = function() {
+ timeout = null;
+ func.apply(context, args);
+ };
+ if (debounce) clearTimeout(timeout);
+ if (debounce || !timeout) timeout = setTimeout(throttler, wait);
+ };
+ };
+
+ // Returns a function, that, when invoked, will only be triggered at most once
+ // during a given window of time.
+ _.throttle = function(func, wait) {
+ return limit(func, wait, false);
+ };
+
+ // Returns a function, that, as long as it continues to be invoked, will not
+ // be triggered. The function will be called after it stops being called for
+ // N milliseconds.
+ _.debounce = function(func, wait) {
+ return limit(func, wait, true);
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = function(func) {
+ var ran = false, memo;
+ return function() {
+ if (ran) return memo;
+ ran = true;
+ return memo = func.apply(this, arguments);
+ };
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return function() {
+ var args = [func].concat(slice.call(arguments));
+ return wrapper.apply(this, args);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var funcs = slice.call(arguments);
+ return function() {
+ var args = slice.call(arguments);
+ for (var i=funcs.length-1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+ return args[0];
+ };
+ };
+
+ // Object Functions
+ // ----------------
+
+ // Retrieve the names of an object's properties.
+ // Delegates to **ECMAScript 5**'s native `Object.keys`
+ _.keys = nativeKeys || function(obj) {
+ if (obj !== Object(obj)) throw new TypeError('Invalid object');
+ var keys = [];
+ for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ return _.map(obj, _.identity);
+ };
+
+ // Return a sorted list of the function names available on the object.
+ // Aliased as `methods`
+ _.functions = _.methods = function(obj) {
+ return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]);
}).sort();
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ for (var prop in source) obj[prop] = source[prop];
+ });
+ return obj;
+ };
+
+ // Fill in a given object with default properties.
+ _.defaults = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ for (var prop in source) if (obj[prop] == null) obj[prop] = source[prop];
+ });
+ return obj;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in
+ // order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ // Check object identity.
+ if (a === b) return true;
+ // Different types?
+ var atype = typeof(a), btype = typeof(b);
+ if (atype != btype) return false;
+ // Basic equality test (watch out for coercions).
+ if (a == b) return true;
+ // One is falsy and the other truthy.
+ if ((!a && b) || (a && !b)) return false;
+ // Unwrap any wrapped objects.
+ if (a._chain) a = a._wrapped;
+ if (b._chain) b = b._wrapped;
+ // One of them implements an isEqual()?
+ if (a.isEqual) return a.isEqual(b);
+ // Check dates' integer values.
+ if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
+ // Both are NaN?
+ if (_.isNaN(a) && _.isNaN(b)) return false;
+ // Compare regular expressions.
+ if (_.isRegExp(a) && _.isRegExp(b))
+ return a.source === b.source &&
+ a.global === b.global &&
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline;
+ // If a is not an object by this point, we can't handle it.
+ if (atype !== 'object') return false;
+ // Check for different array lengths before comparing contents.
+ if (a.length && (a.length !== b.length)) return false;
+ // Nothing else worked, deep compare the contents.
+ var aKeys = _.keys(a), bKeys = _.keys(b);
+ // Different object sizes?
+ if (aKeys.length != bKeys.length) return false;
+ // Recursive comparison of contents.
+ for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
+ return true;
+ };
+
+ // Is a given array or object empty?
+ _.isEmpty = function(obj) {
+ if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+ for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
+ return true;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType == 1);
+ };
+
+ // Is a given value an array?
+ // Delegates to ECMA5's native Array.isArray
+ _.isArray = nativeIsArray || function(obj) {
+ return toString.call(obj) === '[object Array]';
+ };
+
+ // Is a given variable an arguments object?
+ _.isArguments = function(obj) {
+ return !!(obj && hasOwnProperty.call(obj, 'callee'));
+ };
+
+ // Is a given value a function?
+ _.isFunction = function(obj) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ };
+
+ // Is a given value a string?
+ _.isString = function(obj) {
+ return !!(obj === '' || (obj && obj.charCodeAt &&
obj.substr));
+ };
+
+ // Is a given value a number?
+ _.isNumber = function(obj) {
+ return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
+ };
+
+ // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
+ // that does not equal itself.
+ _.isNaN = function(obj) {
+ return obj !== obj;
+ };
+
+ // Is a given value a boolean?
+ _.isBoolean = function(obj) {
+ return obj === true || obj === false;
+ };
+
+ // Is a given value a date?
+ _.isDate = function(obj) {
+ return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
+ };
+
+ // Is the given value a regular expression?
+ _.isRegExp = function(obj) {
+ return !!(obj && obj.test && obj.exec && (obj.ignoreCase ||
obj.ignoreCase === false));
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ // Utility Functions
+ // -----------------
+
+ // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iterators.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Run a function **n** times.
+ _.times = function (n, iterator, context) {
+ for (var i = 0; i < n; i++) iterator.call(context, i);
+ };
+
+ // Add your own custom functions to the Underscore object, ensuring that
+ // they're correctly added to the OOP wrapper as well.
+ _.mixin = function(obj) {
+ each(_.functions(obj), function(name){
+ addToWrapper(name, _[name] = obj[name]);
+ });
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = idCounter++;
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ evaluate : /<%([\s\S]+?)%>/g,
+ interpolate : /<%=([\s\S]+?)%>/g
+ };
+
+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ _.template = function(str, data) {
+ var c = _.templateSettings;
+ var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
+ 'with(obj||{}){__p.push(\'' +
+ str.replace(/\\/g, '\\\\')
+ .replace(/'/g, "\\'")
+ .replace(c.interpolate, function(match, code) {
+ return "'," + code.replace(/\\'/g, "'") +
",'";
+ })
+ .replace(c.evaluate || null, function(match, code) {
+ return "');" + code.replace(/\\'/g, "'")
+ .replace(/[\r\n\t]/g, ' ') +
"__p.push('";
+ })
+ .replace(/\r/g, '\\r')
+ .replace(/\n/g, '\\n')
+ .replace(/\t/g, '\\t')
+ + "');}return __p.join('');";
+ var func = new Function('obj', tmpl);
+ return data ? func(data) : func;
+ };
+
+ // The OOP Wrapper
+ // ---------------
+
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+ var wrapper = function(obj) { this._wrapped = obj; };
+
+ // Expose `wrapper.prototype` as `_.prototype`
+ _.prototype = wrapper.prototype;
+
+ // Helper function to continue chaining intermediate results.
+ var result = function(obj, chain) {
+ return chain ? _(obj).chain() : obj;
+ };
+
+ // A method to easily add functions to the OOP wrapper.
+ var addToWrapper = function(name, func) {
+ wrapper.prototype[name] = function() {
+ var args = slice.call(arguments);
+ unshift.call(args, this._wrapped);
+ return result(func.apply(_, args), this._chain);
+ };
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.mixin(_);
+
+ // Add all mutator Array functions to the wrapper.
+ each(['pop', 'push', 'reverse', 'shift',
'sort', 'splice', 'unshift'], function(name) {
+ var method = ArrayProto[name];
+ wrapper.prototype[name] = function() {
+ method.apply(this._wrapped, arguments);
+ return result(this._wrapped, this._chain);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ each(['concat', 'join', 'slice'], function(name) {
+ var method = ArrayProto[name];
+ wrapper.prototype[name] = function() {
+ return result(method.apply(this._wrapped, arguments), this._chain);
+ };
+ });
+
+ // Start chaining a wrapped Underscore object.
+ wrapper.prototype.chain = function() {
+ this._chain = true;
+ return this;
+ };
+
+ // Extracts the result from a wrapped and chained object.
+ wrapper.prototype.value = function() {
+ return this._wrapped;
+ };
+
+})();
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/visualsearch-datauri.css
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/visualsearch-datauri.css
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/visualsearch-datauri.css 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1 @@
+.VS-search .VS-icon{background-repeat:no-repeat;background-position:center
center;vertical-align:middle;width:16px;height:16px}.VS-search
.VS-icon-cancel{width:11px;height:11px;background-position:center
0;background-image:url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAWCAYAAAAW5GZjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAb9JREFUeNqUUr1qAkEQ3j0khQp6kihaeGgEEa18gTQR0iRY+BaBSMDGwidIEUKqFL6BopgqBAJ5AMFGjUU0d4WHEvwJarvZ77gRIzGYgb1hZr+Z75vZ40IIzqTNZrPj8Xicn0wmmcViEXS73aaqqq+BQODG6/W+A8MBNk3zfDAY3C6Xy0O2ZS6X6zMSiVwHg8FHLjtq7Xb7RQKj7BeTzVCgJ5PJU2U0GhUk7REuMpkMi8fjFggeMeecrVYrFRId0CgTAgDDMFg4HLbA8IjJgHNgGEr0er0fQIphUmZAwdSUADUB4RFDsz3oSMF6CLzZkQqgGebz+Z75dDqNdTqdp13bgDmdTj2VSp0oWHg0Gr2UNH2Z/9o+yMv7K4/HY/C/XhDUfr//jl7QQVT9fp/V63VWqVRYt9tliUSCZbPZg1wux9Lp9PqFeK1Wu9A0DdXz7YM87i0FrVZLs4Fi1wmFQh/NZjOmVKvVgq7rR/QflMtlixGedjwcDlUpMQ9tbzalkAAB2/R297mNW+sT2wUbUnA//V/nYrH4QOBNABUQuFQq3TNMuc82sDVrz41G42yvPeODAwZQ0QzwiJEnzLcAAwBJ6WXlwoBgZAAAAABJRU5ErkJggg==!
");cursor:pointer}.VS-search .VS-icon-cancel:hover{background-position:center
-11px}.VS-search
.VS-icon-search{width:12px;height:12px;background-image:url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUZJREFUeNpUUM2qgmAQzS8NiUgLzTIXLZQW1QuI9AY9QPSW9gQ9QiriwpJQEBVrVWT2d7p2L9xZzDdzZs7M+YYqy/J8Ptu2vd/v4zgeDAaqqk4mE47jar9GnU6nzWbjOA5FUa/Xq0Jns9l8Pud5vkpp58cwAOzhcBhFkeu6GNztdg3D+Db5vo9nOp2iiWGYTqdDCMFe4LquI0aVpGmKR9M0lmUbjQY8YiBJklTb4YkoilBzOBzq9TogeMQIJEmqmlAlo9EIyXa7tSyrKAp4xEBkWUb5q2k8Hh+PR8/zwjCEgufz+aESstvtoKnVan2GgY31kBkEAfT1ej1FUZDiNIIgrFYr9H1ug3teLpfH43G/3/FBUJGu1+s8z8FZLpc0mmiabrfbf5fEumazuVgsTNO8Xq+3242qRNT+G0CMz7IMzH6//xZgAA60tj6rqzxpAAAAAElFTkSuQmCC")}.VS-search
div,.VS-search span,.VS-search a,.VS-search img,.VS-search ul,.VS-search li,.VS-search
form,.VS-search label,.VS-interface ul,.VS-interface
li,.VS-interface{margin:0;padding:0;border:0;outline:0;font-weight:inherit;font-st!
yle:inherit;font-size:100%;font-family:inherit;vertical-align:!
baseline
}.VS-search :focus{outline:0}.VS-search{line-height:1;color:black}.VS-search ol,.VS-search
ul{list-style:none}.VS-search{font-family:Arial,sans-serif;color:#373737;font-size:12px}.VS-search
input{display:block;border:none;outline:none;margin:0;padding:4px;background:transparent;font-size:16px;line-height:20px;width:100%}.VS-interface,.VS-search
.dialog,.VS-search input{font-family:"Lucida Grande","Lucida Sans
Unicode",Helvetica,Arial,sans-serif!important;line-height:1.1em}.VS-search
.VS-search-box{cursor:text;position:relative;background:transparent;border:2px solid
#ccc;border-radius:16px;-webkit-border-radius:16px;-moz-border-radius:16px;background-color:#fafafa;-webkit-box-shadow:inset
0 0 3px #ccc;-moz-box-shadow:inset 0 0 3px #ccc;box-shadow:inset 0 0 3px
#ccc;min-height:28px;height:auto}.VS-search
.VS-search-box.VS-focus{border-color:#acf;-webkit-box-shadow:inset 0 0 3px
#acf;-moz-box-shadow:inset 0 0 3px #acf;box-shadow:inset 0 0 3px #acf}.VS-search
.VS-search-inner{p!
osition:relative;margin:0 20px 0 22px;overflow:hidden}.VS-search
input{width:100px}.VS-search input,.VS-search .VS-input-width-tester{padding:6px
0;float:left;color:#808080;font:13px/17px Helvetica,Arial}.VS-search.VS-focus
input{color:#606060}.VS-search
.VS-icon-search{position:absolute;left:9px;top:8px}.VS-search
.VS-icon-cancel{position:absolute;right:9px;top:8px}.VS-search
.search_facet{float:left;margin:0;padding:0 0 0 14px;position:relative;border:1px solid
transparent;height:20px;margin:3px -3px 3px 0}.VS-search
.search_facet.is_selected{margin-left:-3px;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px;background-color:#d2e6fd;background-image:-moz-linear-gradient(top,#d2e6fd,#b0d1f9);background-image:-webkit-gradient(linear,left
top,left
bottom,from(#d2e6fd),to(#b0d1f9));background-image:linear-gradient(top,#d2e6fd,#b0d1f9);border:1px
solid #6eadf5}.VS-search .search_facet
.category{float:left;text-transform:uppercase;font-weight:bold;font-size:!
10px;color:#808080;padding:8px 0 5px;line-height:13px;cursor:p!
ointer;p
adding:4px 0 0}.VS-search .search_facet.is_selected .category{margin-left:3px}.VS-search
.search_facet .search_facet_input_container{float:left}.VS-search .search_facet
input{margin:0;padding:0;color:#000;font-size:13px;line-height:16px;padding:5px 0 5px
4px;height:16px;width:auto;z-index:100;position:relative;padding-top:1px;padding-bottom:2px;padding-right:3px}.VS-search
.search_facet.is_editing input,.VS-search .search_facet.is_selected
input{color:#000}.VS-search .search_facet
.search_facet_remove{position:absolute;left:0;top:4px}.VS-search .search_facet.is_selected
.search_facet_remove{opacity:.4;left:3px;filter:alpha(opacity=40);background-position:center
-11px}.VS-search .search_facet .search_facet_remove:hover{opacity:1}.VS-search
.search_facet.is_editing .category,.VS-search .search_facet.is_selected
.category{color:#000}.VS-search .search_facet.search_facet_maybe_delete
.category,.VS-search .search_facet.search_facet_maybe_delete
input{color:darkred}.VS-search .sea!
rch_input{height:28px;float:left;margin-left:-1px}.VS-search .search_input
input{padding:6px 3px 6px
2px;line-height:10px;height:22px;margin-top:-4px;width:10px;z-index:100;min-width:4px;position:relative}.VS-search
.search_input.is_editing
input{color:#202020}.VS-interface.ui-autocomplete{position:absolute;border:1px solid
#c0c0c0;border-top:1px solid
#d9d9d9;background-color:#f6f6f6;cursor:pointer;z-index:10000;width:auto;min-width:80px;max-width:220px;max-height:240px;overflow-y:auto;overflow-x:hidden;font-size:13px;top:5px;opacity:.97;box-shadow:3px
4px 5px -2px rgba(0,0,0,0.5);-webkit-box-shadow:3px 4px 5px -2px
rgba(0,0,0,0.5);-moz-box-shadow:3px 4px 5px -2px
rgba(0,0,0,0.5)}.VS-interface.ui-autocomplete
.ui-autocomplete-category{text-transform:capitalize;font-size:11px;padding:4px 4px
4px;border-top:1px solid #a2a2a2;border-bottom:1px solid
#a2a2a2;background-color:#b7b7b7;text-shadow:0 -1px 0
#999;font-weight:bold;color:white;cursor:default}.VS-interface.ui-autocomp!
lete .ui-menu-item a{color:#000;outline:none;display:block;pad!
ding:3px
4px
5px;background-color:#f8f8f8;background-image:-moz-linear-gradient(top,#f8f8f8,#f3f3f3);background-image:-webkit-gradient(linear,left
top,left
bottom,from(#f8f8f8),to(#f3f3f3));background-image:linear-gradient(top,#f8f8f8,#f3f3f3);border-top:1px
solid #fafafa;border-bottom:1px solid #f0f0f0}.VS-interface.ui-autocomplete .ui-menu-item
a:active{outline:none}.VS-interface.ui-autocomplete .ui-menu-item
.ui-state-hover{background-color:#6483f7;background-image:-moz-linear-gradient(top,#648bf5,#2465f3);background-image:-webkit-gradient(linear,left
top,left
bottom,from(#648bf5),to(#2465f3));background-image:linear-gradient(top,#648bf5,#2465f3);border-top:1px
solid #5b83ec;border-bottom:1px solid #1459e9;color:white}.VS-interface.ui-autocomplete
li{list-style:none;width:auto}
\ No newline at end of file
Added:
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/visualsearch.js
===================================================================
---
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/visualsearch.js
(rev 0)
+++
sandbox/trunk/ui/visualsearch/ui/src/main/resources/META-INF/resources/visualsearch.js 2011-09-23
08:44:46 UTC (rev 22734)
@@ -0,0 +1,1994 @@
+// This is the annotated source code for
+// [
VisualSearch.js](http://documentcloud.github.com/visualsearch/),
+// a rich search box for real data.
+//
+// The annotated source HTML is generated by
+// [
Docco](http://jashkenas.github.com/docco/).
+
+/** @license VisualSearch.js 0.2.0
+ * (c) 2011 Samuel Clay, @samuelclay, DocumentCloud Inc.
+ * VisualSearch.js may be freely distributed under the MIT license.
+ * For all details and documentation:
+ *
http://documentcloud.github.com/visualsearch
+ */
+
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // Setting up VisualSearch globals. These will eventually be made instance-based.
+ if (!window.VS) {
+ window.VS = {};
+ }
+ if (!VS.app) {
+ VS.app = {};
+ }
+ if (!VS.ui) {
+ VS.ui = {};
+ }
+ if (!VS.model) {
+ VS.model = {};
+ }
+ if (!VS.utils) {
+ VS.utils = {};
+ }
+
+ // Sets the version for VisualSearch to be used programatically elsewhere.
+ VS.VERSION = '0.2.0';
+
+ VS.VisualSearch = function(options)
+ {
+ var defaults = {
+ container : '',
+ query : '',
+ unquotable : [],
+ callbacks : {
+ search : $.noop,
+ focus : $.noop,
+ facetMatches : $.noop,
+ valueMatches : $.noop
+ }
+ };
+ this.options = _.extend({}, defaults, options);
+ this.options.callbacks = _.extend({}, defaults.callbacks, options.callbacks);
+
+ VS.app.hotkeys.initialize();
+ this.searchQuery = new VS.model.SearchQuery();
+ this.searchBox = new VS.ui.SearchBox({app: this});
+
+ if (options.container) {
+ var searchBox = this.searchBox.render().el;
+ $(this.options.container).html(searchBox);
+ }
+ this.searchBox.value(this.options.query || '');
+
+ // Disable page caching for browsers that incorrectly cache the visual search
inputs.
+ // This is forced the browser to re-render the page when it is retrieved in its
history.
+ $(window).bind('unload', function(e)
+ {
+ });
+
+ // Gives the user back a reference to the `searchBox` so they
+ // can use public methods.
+ return this;
+ };
+
+ // Entry-point used to tie all parts of VisualSearch together. It will either attach
+ // itself to `options.container`, or pass back the `searchBox` so it can be rendered
+ // at will.
+ VS.init = function(options)
+ {
+ return new VS.VisualSearch(options);
+ };
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // The search box is responsible for managing the many facet views and input views.
+ VS.ui.SearchBox = Backbone.View.extend({
+
+ id : 'search',
+
+ events : {
+ 'click .VS-cancel-search-box' : 'clearSearch',
+ 'mousedown .VS-search-box' : 'maybeFocusSearch',
+ 'dblclick .VS-search-box' : 'highlightSearch',
+ 'click .VS-search-box' : 'maybeTripleClick'
+ },
+
+ // Creating a new SearchBox registers handlers for re-rendering facets when
necessary,
+ // as well as handling typing when a facet is selected.
+ initialize : function()
+ {
+ this.app = this.options.app;
+ this.flags = {
+ allSelected : false
+ };
+ this.facetViews = [];
+ this.inputViews = [];
+ _.bindAll(this, 'renderFacets', '_maybeDisableFacets',
'disableFacets', 'deselectAllFacets');
+ this.app.searchQuery.bind('reset', this.renderFacets);
+ $(document).bind('keydown', this._maybeDisableFacets);
+ },
+
+ // Renders the search box, but requires placement on the page through `this.el`.
+ render : function()
+ {
+ $(this.el).append(JST['search_box']({}));
+ $(document.body).setMode('no', 'search');
+
+ return this;
+ },
+
+ // # Querying Facets #
+
+ // Either gets a serialized query string or sets the faceted query from a query
string.
+ value : function(query)
+ {
+ if (query == null) {
+ return this.serialize();
+ }
+ return this.setQuery(query);
+ },
+
+ // Uses the VS.app.searchQuery collection to serialize the current query from the
various
+ // facets that are in the search box.
+ serialize : function()
+ {
+ var query = [];
+ var inputViewsCount = this.inputViews.length;
+
+ this.app.searchQuery.each(_.bind(function(facet, i)
+ {
+ query.push(this.inputViews[i].value());
+ query.push(facet.serialize());
+ }, this));
+
+ if (inputViewsCount) {
+ query.push(this.inputViews[inputViewsCount - 1].value());
+ }
+
+ return _.compact(query).join(' ');
+ },
+
+ // Takes a query string and uses the SearchParser to parse and render it. Note
that
+ // `VS.app.SearchParser` refreshes the `VS.app.searchQuery` collection, which is
bound
+ // here to call `this.renderFacets`.
+ setQuery : function(query)
+ {
+ this.currentQuery = query;
+ VS.app.SearchParser.parse(this.app, query);
+ },
+
+ // Returns the position of a facet/input view. Useful when moving between
facets.
+ viewPosition : function(view)
+ {
+ var views = view.type == 'facet' ? this.facetViews :
this.inputViews;
+ var position = _.indexOf(views, view);
+ if (position == -1) {
+ position = 0;
+ }
+ return position;
+ },
+
+ // Used to launch a search. Hitting enter or clicking the search button.
+ searchEvent : function(e)
+ {
+ var query = this.value();
+ this.focusSearch(e);
+ this.value(query);
+ this.app.options.callbacks.search(query, this.app.searchQuery);
+ },
+
+ // # Rendering Facets #
+
+ // Add a new facet. Facet will be focused and ready to accept a value. Can also
+ // specify position, in the case of adding facets from an inbetween input.
+ addFacet : function(category, initialQuery, position)
+ {
+ category = VS.utils.inflector.trim(category);
+ initialQuery = VS.utils.inflector.trim(initialQuery || '');
+ if (!category) {
+ return;
+ }
+
+ var model = new VS.model.SearchFacet({
+ category : category,
+ value : initialQuery || '',
+ app : this.app
+ });
+ this.app.searchQuery.add(model, {at: position});
+ this.renderFacets();
+ var facetView = _.detect(this.facetViews, function(view)
+ {
+ if (view.model == model) {
+ return true;
+ }
+ });
+
+ _.defer(function()
+ {
+ facetView.enableEdit();
+ });
+ },
+
+ // Renders each facet as a searchFacet view.
+ renderFacets : function()
+ {
+ this.facetViews = [];
+ this.inputViews = [];
+
+ this.$('.VS-search-inner').empty();
+
+ this.app.searchQuery.each(_.bind(function(facet, i)
+ {
+ this.renderFacet(facet, i);
+ }, this));
+
+ // Add on an n+1 empty search input on the very end.
+ this.renderSearchInput();
+ },
+
+ // Render a single facet, using its category and query value.
+ renderFacet : function(facet, position)
+ {
+ var view = new VS.ui.SearchFacet({
+ app : this.app,
+ model : facet,
+ order : position
+ });
+
+ // Input first, facet second.
+ this.renderSearchInput();
+ this.facetViews.push(view);
+ this.$('.VS-search-inner').children().eq(position *
2).after(view.render().el);
+
+ view.calculateSize();
+ _.defer(_.bind(view.calculateSize, view));
+
+ return view;
+ },
+
+ // Render a single input, used to create and autocomplete facets
+ renderSearchInput : function()
+ {
+ var input = new VS.ui.SearchInput({position: this.inputViews.length, app:
this.app});
+ this.$('.VS-search-inner').append(input.render().el);
+ this.inputViews.push(input);
+ },
+
+ // # Modifying Facets #
+
+ // Clears out the search box. Command+A + delete can trigger this, as can a
cancel button.
+ //
+ // If a `clearSearch` callback was provided, the callback is invoked and
+ // provided with a function performs the actual removal of the data. This
+ // allows third-party developers to either clear data asynchronously, or
+ // prior to performing their custom "clear" logic.
+ clearSearch : function(e)
+ {
+ var actualClearSearch = _.bind(function()
+ {
+ this.disableFacets();
+ this.value('');
+ this.flags.allSelected = false;
+ this.focusSearch(e);
+ }, this);
+
+ if (this.app.options.callbacks.clearSearch) {
+ this.app.options.callbacks.clearSearch(actualClearSearch);
+ } else {
+ actualClearSearch();
+ }
+ },
+
+ // Command+A selects all facets.
+ selectAllFacets : function()
+ {
+ this.flags.allSelected = true;
+
+ $(document).one('click.selectAllFacets', this.deselectAllFacets);
+
+ _.each(this.facetViews, function(facetView, i)
+ {
+ facetView.selectFacet();
+ });
+ _.each(this.inputViews, function(inputView, i)
+ {
+ inputView.selectText();
+ });
+ },
+
+ // Used by facets and input to see if all facets are currently selected.
+ allSelected : function(deselect)
+ {
+ if (deselect) {
+ this.flags.allSelected = false;
+ }
+ return this.flags.allSelected;
+ },
+
+ // After `selectAllFacets` is engaged, this method is bound to the entire
document.
+ // This immediate disables and deselects all facets, but it also checks if the
user
+ // has clicked on either a facet or an input, and properly selects the view.
+ deselectAllFacets : function(e)
+ {
+ this.disableFacets();
+
+ if (this.$(e.target).is('.category,input')) {
+ var el = $(e.target).closest('.search_facet,.search_input');
+ var view = _.detect(this.facetViews.concat(this.inputViews), function(v)
+ {
+ return v.el == el[0];
+ });
+ if (view.type == 'facet') {
+ view.selectFacet();
+ } else {
+ if (view.type == 'input') {
+ _.defer(function()
+ {
+ view.enableEdit(true);
+ });
+ }
+ }
+ }
+ },
+
+ // Disables all facets except for the passed in view. Used when switching
between
+ // facets, so as not to have to keep state of active facets.
+ disableFacets : function(keepView)
+ {
+ _.each(this.inputViews, function(view)
+ {
+ if (view && view != keepView && (view.modes.editing ==
'is' || view.modes.selected == 'is')) {
+ view.disableEdit();
+ }
+ });
+ _.each(this.facetViews, function(view)
+ {
+ if (view && view != keepView && (view.modes.editing ==
'is' || view.modes.selected == 'is')) {
+ view.disableEdit();
+ view.deselectFacet();
+ }
+ });
+
+ this.flags.allSelected = false;
+ this.removeFocus();
+ $(document).unbind('click.selectAllFacets');
+ },
+
+ // Resize all inputs to account for extra keystrokes which may be changing the
facet
+ // width incorrectly. This is a safety check to ensure inputs are correctly
sized.
+ resizeFacets : function(view)
+ {
+ _.each(this.facetViews, function(facetView, i)
+ {
+ if (!view || facetView == view) {
+ facetView.resize();
+ }
+ });
+ },
+
+ // Handles keydown events on the document. Used to complete the Cmd+A deletion,
and
+ // blurring focus.
+ _maybeDisableFacets : function(e)
+ {
+ if (this.flags.allSelected && VS.app.hotkeys.key(e) ==
'backspace') {
+ e.preventDefault();
+ this.clearSearch(e);
+ return false;
+ } else {
+ if (this.flags.allSelected && VS.app.hotkeys.printable(e)) {
+ this.clearSearch(e);
+ }
+ }
+ },
+
+ // # Focusing Facets #
+
+ // Move focus between facets and inputs. Takes a direction as well as many
options
+ // for skipping over inputs and only to facets, placement of cursor position in
facet
+ // (i.e. at the end), and selecting the text in the input/facet.
+ focusNextFacet : function(currentView, direction, options)
+ {
+ options = options || {};
+ var viewCount = this.facetViews.length;
+ var viewPosition = options.viewPosition || this.viewPosition(currentView);
+
+ if (!options.skipToFacet) {
+ // Correct for bouncing between matching text and facet arrays.
+ if (currentView.type == 'text' && direction > 0) {
+ direction -= 1;
+ }
+ if (currentView.type == 'facet' && direction < 0) {
+ direction += 1;
+ }
+ } else {
+ if (options.skipToFacet && currentView.type == 'text'
&& viewCount == viewPosition && direction >= 0) {
+ // Special case of looping around to a facet from the last search
input box.
+ viewPosition = 0;
+ direction = 0;
+ }
+ }
+ var view, next = Math.min(viewCount, viewPosition + direction);
+
+ if (currentView.type == 'text') {
+ if (next >= 0 && next < viewCount) {
+ view = this.facetViews[next];
+ } else {
+ if (next == viewCount) {
+ view = this.inputViews[this.inputViews.length - 1];
+ }
+ }
+ if (view && options.selectFacet && view.type ==
'facet') {
+ view.selectFacet();
+ } else {
+ if (view) {
+ view.enableEdit();
+ view.setCursorAtEnd(direction || options.startAtEnd);
+ }
+ }
+ } else {
+ if (currentView.type == 'facet') {
+ if (options.skipToFacet) {
+ if (next >= viewCount || next < 0) {
+ view = _.last(this.inputViews);
+ view.enableEdit();
+ } else {
+ view = this.facetViews[next];
+ view.enableEdit();
+ view.setCursorAtEnd(direction || options.startAtEnd);
+ }
+ } else {
+ view = this.inputViews[next];
+ view.enableEdit();
+ }
+ }
+ }
+ if (options.selectText) {
+ view.selectText();
+ }
+ this.resizeFacets();
+ },
+
+ maybeFocusSearch : function(e)
+ {
+ if ($(e.target).is('.VS-search-box') ||
$(e.target).is('.VS-search-inner') || e.type == 'keydown') {
+ this.focusSearch(e);
+ }
+ },
+
+ // Bring focus to last input field.
+ focusSearch : function(e, selectText)
+ {
+ var view = this.inputViews[this.inputViews.length - 1];
+ view.enableEdit(selectText);
+ if (!selectText) {
+ view.setCursorAtEnd(-1);
+ }
+ if (e.type == 'keydown') {
+ view.keydown(e);
+ view.box.trigger('keydown');
+ }
+ _.defer(_.bind(function()
+ {
+ if (!this.$('input:focus').length) {
+ view.enableEdit(selectText);
+ }
+ }, this));
+ },
+
+ // Double-clicking on the search wrapper should select the existing text in
+ // the last search input. Also start the triple-click timer.
+ highlightSearch : function(e)
+ {
+ if ($(e.target).is('.VS-search-box') ||
$(e.target).is('.VS-search-inner') || e.type == 'keydown') {
+ var lastinput = this.inputViews[this.inputViews.length - 1];
+ lastinput.startTripleClickTimer();
+ this.focusSearch(e, true);
+ }
+ },
+
+ maybeTripleClick : function(e)
+ {
+ var lastinput = this.inputViews[this.inputViews.length - 1];
+ return lastinput.maybeTripleClick(e);
+ },
+
+ // Used to show the user is focused on some input inside the search box.
+ addFocus : function()
+ {
+ this.app.options.callbacks.focus();
+ this.$('.VS-search-box').addClass('VS-focus');
+ },
+
+ // User is no longer focused on anything in the search box.
+ removeFocus : function()
+ {
+ var focus = _.any(this.facetViews.concat(this.inputViews), function(view)
+ {
+ return view.isFocused();
+ });
+ if (!focus) {
+ this.$('.VS-search-box').removeClass('VS-focus');
+ }
+ },
+
+ // Show a menu which adds pre-defined facets to the search box. This is unused
for now.
+ showFacetCategoryMenu : function(e)
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ if (this.facetCategoryMenu && this.facetCategoryMenu.modes.open ==
'is') {
+ return this.facetCategoryMenu.close();
+ }
+
+ var items = [
+ {title: 'Account', onClick: _.bind(this.addFacet, this,
'account', '')},
+ {title: 'Project', onClick: _.bind(this.addFacet, this,
'project', '')},
+ {title: 'Filter', onClick: _.bind(this.addFacet, this,
'filter', '')},
+ {title: 'Access', onClick: _.bind(this.addFacet, this,
'access', '')}
+ ];
+
+ var menu = this.facetCategoryMenu || (this.facetCategoryMenu = new
dc.ui.Menu({
+ items : items,
+ standalone : true
+ }));
+
+ this.$('.VS-icon-search').after(menu.render().open().content);
+ return false;
+ }
+
+ });
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // This is the visual search facet that holds the category and its autocompleted
+ // input field.
+ VS.ui.SearchFacet = Backbone.View.extend({
+
+ type : 'facet',
+
+ className : 'search_facet',
+
+ events : {
+ 'click .category' : 'selectFacet',
+ 'keydown input' : 'keydown',
+ 'mousedown input' : 'enableEdit',
+ 'mouseover .VS-icon-cancel' : 'showDelete',
+ 'mouseout .VS-icon-cancel' : 'hideDelete',
+ 'click .VS-icon-cancel' : 'remove'
+ },
+
+ initialize : function(options)
+ {
+ this.flags = {
+ canClose : false
+ };
+ _.bindAll(this, 'set', 'keydown', 'deselectFacet',
'deferDisableEdit');
+ },
+
+ // Rendering the facet sets up autocompletion, events on blur, and populates
+ // the facet's input with its starting value.
+ render : function()
+ {
+ $(this.el).html(JST['search_facet']({
+ model : this.model
+ }));
+
+ this.setMode('not', 'editing');
+ this.setMode('not', 'selected');
+ this.box = this.$('input');
+ this.box.val(this.model.get('value'));
+ this.box.bind('blur', this.deferDisableEdit);
+ // Handle paste events with `propertychange`
+ this.box.bind('input propertychange', this.keydown);
+ this.setupAutocomplete();
+
+ return this;
+ },
+
+ // This method is used to setup the facet's input to auto-grow.
+ // This is defered in the searchBox so it can be attached to the
+ // DOM to get the correct font-size.
+ calculateSize : function()
+ {
+ this.box.autoGrowInput();
+ this.box.unbind('updated.autogrow');
+ this.box.bind('updated.autogrow', _.bind(this.moveAutocomplete,
this));
+ },
+
+ // Forces a recalculation of this facet's input field's value. Called
when
+ // the facet is focused, removed, or otherwise modified.
+ resize : function(e)
+ {
+ this.box.trigger('resize.autogrow', e);
+ },
+
+ // Watches the facet's input field to see if it matches the beginnings of
+ // words in `autocompleteValues`, which is different for every category.
+ // If the value, when selected from the autocompletion menu, is different
+ // than what it was, commit the facet and search for it.
+ setupAutocomplete : function()
+ {
+ this.box.autocomplete({
+ source : _.bind(this.autocompleteValues, this),
+ minLength : 0,
+ delay : 0,
+ autoFocus : true,
+ position : {offset : "0 5"},
+ select : _.bind(function(e, ui)
+ {
+ e.preventDefault();
+ var originalValue = this.model.get('value');
+ this.set(ui.item.value);
+ if (originalValue != ui.item.value || this.box.val() !=
ui.item.value) {
+ this.search(e);
+ }
+ return false;
+ }, this)
+ });
+
+ this.box.autocomplete('widget').addClass('VS-interface');
+ },
+
+ // As the facet's input field grows, it may move to the next line in the
+ // search box. `autoGrowInput` triggers an `updated` event on the input
+ // field, which is bound to this method to move the autocomplete menu.
+ moveAutocomplete : function()
+ {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) {
+ autocomplete.menu.element.position({
+ my : "left top",
+ at : "left bottom",
+ of : this.box.data('autocomplete').element,
+ collision : "flip",
+ offset : "0 5"
+ });
+ }
+ },
+
+ // When a user enters a facet and it is being edited, immediately show
+ // the autocomplete menu and size it to match the contents.
+ searchAutocomplete : function(e)
+ {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) {
+ var menu = autocomplete.menu.element;
+ autocomplete.search();
+
+ // Resize the menu based on the correctly measured width of what's
bigger:
+ // the menu's original size or the menu items' new size.
+ menu.outerWidth(Math.max(menu.width('').outerWidth(),
autocomplete.element.outerWidth()));
+ }
+ },
+
+ // Closes the autocomplete menu. Called on disabling, selecting, deselecting,
+ // and anything else that takes focus out of the facet's input field.
+ closeAutocomplete : function()
+ {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) {
+ autocomplete.close();
+ }
+ },
+
+ // Search terms used in the autocomplete menu. These are specific to the facet,
+ // and only match for the facet's category. The values are then matched on
the
+ // first letter of any word in matches, and finally sorted according to the
+ // value's own category.
+ autocompleteValues : function(req, resp)
+ {
+ var category = this.model.get('category');
+ var value = this.model.get('value');
+ var searchTerm = req.term;
+
+ this.options.app.options.callbacks.valueMatches(category, searchTerm,
function(matches)
+ {
+ matches = matches || [];
+ if (searchTerm && value != searchTerm) {
+ var re = VS.utils.inflector.escapeRegExp(searchTerm || '');
+ var matcher = new RegExp('\\b' + re, 'i');
+ matches = $.grep(matches, function(item)
+ {
+ return matcher.test(item) || matcher.test(item.value) ||
matcher.test(item.label);
+ });
+ }
+
+ resp(_.sortBy(matches, function(match)
+ {
+ if (match == value || match.value == value) {
+ return '';
+ } else {
+ return match;
+ }
+ }));
+ });
+
+ },
+
+ // Sets the facet's model's value.
+ set : function(value)
+ {
+ if (!value) {
+ return;
+ }
+ this.model.set({'value': value});
+ },
+
+ // Before the searchBox performs a search, we need to close the
+ // autocomplete menu.
+ search : function(e, direction)
+ {
+ if (!direction) {
+ direction = 1;
+ }
+ this.closeAutocomplete();
+ this.options.app.searchBox.searchEvent(e);
+ _.defer(_.bind(function()
+ {
+ this.options.app.searchBox.focusNextFacet(this, direction, {viewPosition:
this.options.order});
+ }, this));
+ },
+
+ // Begin editing the facet's input. This is called when the user enters
+ // the input either from another facet or directly clicking on it.
+ //
+ // This method tells all other facets and inputs to disable so it can have
+ // the sole focus. It also prepares the autocompletion menu.
+ enableEdit : function()
+ {
+ if (this.modes.editing != 'is') {
+ this.setMode('is', 'editing');
+ this.deselectFacet();
+ if (this.box.val() == '') {
+ this.box.val(this.model.get('value'));
+ }
+ }
+
+ this.flags.canClose = false;
+ this.options.app.searchBox.disableFacets(this);
+ this.options.app.searchBox.addFocus();
+ _.defer(_.bind(function()
+ {
+ this.options.app.searchBox.addFocus();
+ }, this));
+ this.resize();
+ this.searchAutocomplete();
+ this.box.focus();
+ },
+
+ // When the user blurs the input, they may either be going to another input
+ // or off the search box entirely. If they go to another input, this facet
+ // will be instantly disabled, and the canClose flag will be turned back off.
+ //
+ // However, if the user clicks elsewhere on the page, this method starts a timer
+ // that checks if any of the other inputs are selected or are being edited. If
+ // not, then it can finally close itself and its autocomplete menu.
+ deferDisableEdit : function()
+ {
+ this.flags.canClose = true;
+ _.delay(_.bind(function()
+ {
+ if (this.flags.canClose && !this.box.is(':focus')
&& this.modes.editing == 'is' && this.modes.selected !=
'is') {
+ this.disableEdit();
+ }
+ }, this), 250);
+ },
+
+ // Called either by other facets receiving focus or by the timer in
`deferDisableEdit`,
+ // this method will turn off the facet, remove any text selection, and close
+ // the autocomplete menu.
+ disableEdit : function()
+ {
+ var newFacetQuery = VS.utils.inflector.trim(this.box.val());
+ if (newFacetQuery != this.model.get('value')) {
+ this.set(newFacetQuery);
+ }
+ this.flags.canClose = false;
+ this.box.selectRange(0, 0);
+ this.box.blur();
+ this.setMode('not', 'editing');
+ this.closeAutocomplete();
+ this.options.app.searchBox.removeFocus();
+ },
+
+ // Selects the facet, which blurs the facet's input and highlights the
facet.
+ // If this is the only facet being selected (and not part of a select all
event),
+ // we attach a mouse/keyboard watcher to check if the next action by the user
+ // should delete this facet or just deselect it.
+ selectFacet : function(e)
+ {
+ if (e) {
+ e.preventDefault();
+ }
+ var allSelected = this.options.app.searchBox.allSelected();
+ if (this.modes.selected == 'is') {
+ return;
+ }
+
+ if (this.box.is(':focus')) {
+ this.box.setCursorPosition(0);
+ this.box.blur();
+ }
+
+ this.flags.canClose = false;
+ this.closeAutocomplete();
+ this.setMode('is', 'selected');
+ this.setMode('not', 'editing');
+ if (!allSelected || e) {
+ $(document).unbind('keydown.facet', this.keydown);
+ $(document).unbind('click.facet', this.deselectFacet);
+ _.defer(_.bind(function()
+ {
+
$(document).unbind('keydown.facet').bind('keydown.facet', this.keydown);
+ $(document).unbind('click.facet').one('click.facet',
this.deselectFacet);
+ }, this));
+ this.options.app.searchBox.disableFacets(this);
+ this.options.app.searchBox.addFocus();
+ }
+ return false;
+ },
+
+ // Turns off highlighting on the facet. Called in a variety of ways, this
+ // only deselects the facet if it is selected, and then cleans up the
+ // keyboard/mouse watchers that were created when the facet was first
+ // selected.
+ deselectFacet : function(e)
+ {
+ if (e) {
+ e.preventDefault();
+ }
+ if (this.modes.selected == 'is') {
+ this.setMode('not', 'selected');
+ this.closeAutocomplete();
+ this.options.app.searchBox.removeFocus();
+ }
+ $(document).unbind('keydown.facet', this.keydown);
+ $(document).unbind('click.facet', this.deselectFacet);
+ return false;
+ },
+
+ // Is the user currently focused in this facet's input field?
+ isFocused : function()
+ {
+ return this.box.is(':focus');
+ },
+
+ // Hovering over the delete button styles the facet so the user knows that
+ // the delete button will kill the entire facet.
+ showDelete : function()
+ {
+ $(this.el).addClass('search_facet_maybe_delete');
+ },
+
+ // On `mouseout`, the user is no longer hovering on the delete button.
+ hideDelete : function()
+ {
+ $(this.el).removeClass('search_facet_maybe_delete');
+ },
+
+ // When switching between facets, depending on the direction the cursor is
+ // coming from, the cursor in this facet's input field should match the
original
+ // direction.
+ setCursorAtEnd : function(direction)
+ {
+ if (direction == -1) {
+ this.box.setCursorPosition(this.box.val().length);
+ } else {
+ this.box.setCursorPosition(0);
+ }
+ },
+
+ // Deletes the facet and sends the cursor over to the nearest input field.
+ remove : function(e)
+ {
+ var committed = this.model.get('value');
+ this.deselectFacet();
+ this.disableEdit();
+ this.options.app.searchQuery.remove(this.model);
+ if (committed) {
+ this.search(e, -1);
+ } else {
+ this.options.app.searchBox.renderFacets();
+ this.options.app.searchBox.focusNextFacet(this, -1, {viewPosition:
this.options.order});
+ }
+ },
+
+ // Selects the text in the facet's input field. When the user tabs between
+ // facets, convention is to highlight the entire field.
+ selectText: function()
+ {
+ this.box.selectRange(0, this.box.val().length);
+ },
+
+ // Handles all keyboard inputs when in the facet's input field. This checks
+ // for movement between facets and inputs, entering a new value that needs
+ // to be autocompleted, as well as the removal of this facet.
+ keydown : function(e)
+ {
+ var key = VS.app.hotkeys.key(e);
+
+ if (key == 'enter' && this.box.val()) {
+ this.disableEdit();
+ this.search(e);
+ } else {
+ if (key == 'left') {
+ if (this.modes.selected == 'is') {
+ this.deselectFacet();
+ this.options.app.searchBox.focusNextFacet(this, -1, {startAtEnd:
-1});
+ } else {
+ if (this.box.getCursorPosition() == 0 &&
!this.box.getSelection().length) {
+ this.selectFacet();
+ }
+ }
+ } else {
+ if (key == 'right') {
+ if (this.modes.selected == 'is') {
+ e.preventDefault();
+ this.deselectFacet();
+ this.setCursorAtEnd(0);
+ this.enableEdit();
+ } else {
+ if (this.box.getCursorPosition() == this.box.val().length) {
+ e.preventDefault();
+ this.disableEdit();
+ this.options.app.searchBox.focusNextFacet(this, 1);
+ }
+ }
+ } else {
+ if (VS.app.hotkeys.shift && key == 'tab') {
+ e.preventDefault();
+ this.options.app.searchBox.focusNextFacet(this, -1, {
+ startAtEnd : -1,
+ skipToFacet : true,
+ selectText : true
+ });
+ } else {
+ if (key == 'tab') {
+ e.preventDefault();
+ this.options.app.searchBox.focusNextFacet(this, 1, {
+ skipToFacet : true,
+ selectText : true
+ });
+ } else {
+ if (VS.app.hotkeys.command && (e.which == 97 ||
e.which == 65)) {
+ e.preventDefault();
+ this.options.app.searchBox.selectAllFacets();
+ return false;
+ } else {
+ if (VS.app.hotkeys.printable(e) &&
this.modes.selected == 'is') {
+ this.options.app.searchBox.focusNextFacet(this,
-1, {startAtEnd: -1});
+ this.remove(e);
+ } else {
+ if (key == 'backspace') {
+ if (this.modes.selected == 'is') {
+ e.preventDefault();
+ this.remove(e);
+ } else {
+ if (this.box.getCursorPosition() == 0
&& !this.box.getSelection().length) {
+ e.preventDefault();
+ this.selectFacet();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ this.resize(e);
+
+ // Handle paste events
+ if (e.which == null) {
+ this.searchAutocomplete(e);
+ _.defer(_.bind(this.resize, this, e));
+ }
+ }
+
+ });
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // This is the visual search input that is responsible for creating new facets.
+ // There is one input placed in between all facets.
+ VS.ui.SearchInput = Backbone.View.extend({
+
+ type : 'text',
+
+ className : 'search_input',
+
+ events : {
+ 'keypress input' : 'keypress',
+ 'keydown input' : 'keydown',
+ 'click input' : 'maybeTripleClick',
+ 'dblclick input' : 'startTripleClickTimer'
+ },
+
+ initialize : function()
+ {
+ this.app = this.options.app;
+ this.flags = {
+ canClose : false
+ };
+ _.bindAll(this, 'removeFocus', 'addFocus',
'moveAutocomplete', 'deferDisableEdit');
+ },
+
+ // Rendering the input sets up autocomplete, events on focusing and blurring
+ // the input, and the auto-grow of the input.
+ render : function()
+ {
+ $(this.el).html(JST['search_input']({}));
+
+ this.setMode('not', 'editing');
+ this.setMode('not', 'selected');
+ this.box = this.$('input');
+ this.box.autoGrowInput();
+ this.box.bind('updated.autogrow', this.moveAutocomplete);
+ this.box.bind('blur', this.deferDisableEdit);
+ this.box.bind('focus', this.addFocus);
+ this.setupAutocomplete();
+
+ return this;
+ },
+
+ // Watches the input and presents an autocompleted menu, taking the
+ // remainder of the input field and adding a separate facet for it.
+ //
+ // See `addTextFacetRemainder` for explanation on how the remainder works.
+ setupAutocomplete : function()
+ {
+ this.box.autocomplete({
+ minLength : 1,
+ delay : 50,
+ autoFocus : true,
+ position : {offset : "0 -1"},
+ source : _.bind(this.autocompleteValues, this),
+ select : _.bind(function(e, ui)
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ var remainder = this.addTextFacetRemainder(ui.item.value);
+ var position = this.options.position + (remainder ? 1 : 0);
+ this.app.searchBox.addFacet(ui.item.value, '', position);
+ return false;
+ }, this)
+ });
+
+ // Renders the results grouped by the categories they belong to.
+ this.box.data('autocomplete')._renderMenu = function(ul, items)
+ {
+ var category = '';
+ _.each(items, _.bind(function(item, i)
+ {
+ if (item.category && item.category != category) {
+ ul.append('<li
class="ui-autocomplete-category">' + item.category +
'</li>');
+ category = item.category;
+ }
+ this._renderItem(ul, item);
+ }, this));
+ };
+
+ this.box.autocomplete('widget').addClass('VS-interface');
+ },
+
+ // Search terms used in the autocomplete menu. The values are matched on the
+ // first letter of any word in matches, and finally sorted according to the
+ // value's own category.
+ autocompleteValues : function(req, resp)
+ {
+ var searchTerm = req.term;
+ var lastWord = searchTerm.match(/\w+$/); // Autocomplete only last word.
+ var re = VS.utils.inflector.escapeRegExp(lastWord && lastWord[0] ||
' ');
+ this.app.options.callbacks.facetMatches(searchTerm, function(prefixes)
+ {
+ prefixes = prefixes || [];
+ // Only match from the beginning of the word.
+ var matcher = new RegExp('^' + re, 'i');
+ var matches = $.grep(prefixes, function(item)
+ {
+ return item && matcher.test(item.label || item);
+ });
+
+ resp(_.sortBy(matches, function(match)
+ {
+ if (match.label) return match.category + '-' + match.label;
else return match;
+ }));
+ });
+
+ },
+
+ // Closes the autocomplete menu. Called on disabling, selecting, deselecting,
+ // and anything else that takes focus out of the facet's input field.
+ closeAutocomplete : function()
+ {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) autocomplete.close();
+ },
+
+ // As the input field grows, it may move to the next line in the
+ // search box. `autoGrowInput` triggers an `updated` event on the input
+ // field, which is bound to this method to move the autocomplete menu.
+ moveAutocomplete : function()
+ {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) {
+ autocomplete.menu.element.position({
+ my : "left top",
+ at : "left bottom",
+ of : this.box.data('autocomplete').element,
+ collision : "none",
+ offset : '0 -1'
+ });
+ }
+ },
+
+ // When a user enters a facet and it is being edited, immediately show
+ // the autocomplete menu and size it to match the contents.
+ searchAutocomplete : function(e)
+ {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) {
+ var menu = autocomplete.menu.element;
+ autocomplete.search();
+
+ // Resize the menu based on the correctly measured width of what's
bigger:
+ // the menu's original size or the menu items' new size.
+ menu.outerWidth(Math.max(menu.width('').outerWidth(),
autocomplete.element.outerWidth()));
+ }
+ },
+
+ // If a user searches for "word word category", the category would be
+ // matched and autocompleted, and when selected, the "word word" would
+ // also be caught as the remainder and then added in its own facet.
+ addTextFacetRemainder : function(facetValue)
+ {
+ var boxValue = this.box.val();
+ var lastWord = boxValue.match(/\b(\w+)$/);
+ var matcher = new RegExp(lastWord[0], "i");
+ if (lastWord && facetValue.search(matcher) == 0) {
+ boxValue = boxValue.replace(/\b(\w+)$/, '');
+ }
+ boxValue = boxValue.replace('^\s+|\s+$', '');
+ if (boxValue) {
+ this.app.searchBox.addFacet('text', boxValue,
this.options.position);
+ }
+ return boxValue;
+ },
+
+ // Directly called to focus the input. This is different from `addFocus`
+ // because this is not called by a focus event. This instead calls a
+ // focus event causing the input to become focused.
+ enableEdit : function(selectText)
+ {
+ this.addFocus();
+ if (selectText) {
+ this.selectText();
+ }
+ this.box.focus();
+ },
+
+ // Event called on user focus on the input. Tells all other input and facets
+ // to give up focus, and starts revving the autocomplete.
+ addFocus : function()
+ {
+ this.flags.canClose = false;
+ if (!this.app.searchBox.allSelected()) {
+ this.app.searchBox.disableFacets(this);
+ }
+ this.app.searchBox.addFocus();
+ this.setMode('is', 'editing');
+ this.setMode('not', 'selected');
+ this.searchAutocomplete();
+ },
+
+ // Directly called to blur the input. This is different from `removeFocus`
+ // because this is not called by a blur event.
+ disableEdit : function()
+ {
+ this.box.blur();
+ this.removeFocus();
+ },
+
+ // Event called when user blur's the input, either through the keyboard
tabbing
+ // away or the mouse clicking off. Cleans up
+ removeFocus : function()
+ {
+ this.flags.canClose = false;
+ this.app.searchBox.removeFocus();
+ this.setMode('not', 'editing');
+ this.setMode('not', 'selected');
+ this.closeAutocomplete();
+ },
+
+ // When the user blurs the input, they may either be going to another input
+ // or off the search box entirely. If they go to another input, this facet
+ // will be instantly disabled, and the canClose flag will be turned back off.
+ //
+ // However, if the user clicks elsewhere on the page, this method starts a timer
+ // that checks if any of the other inputs are selected or are being edited. If
+ // not, then it can finally close itself and its autocomplete menu.
+ deferDisableEdit : function()
+ {
+ this.flags.canClose = true;
+ _.delay(_.bind(function()
+ {
+ if (this.flags.canClose && !this.box.is(':focus')
&& this.modes.editing == 'is') {
+ this.disableEdit();
+ }
+ }, this), 250);
+ },
+
+ // Starts a timer that will cause a triple-click, which highlights all facets.
+ startTripleClickTimer : function()
+ {
+ this.tripleClickTimer = setTimeout(_.bind(function()
+ {
+ this.tripleClickTimer = null;
+ }, this), 500);
+ },
+
+ // Event on click that checks if a triple click is in play. The
+ // `tripleClickTimer` is counting down, ready to be engaged and intercept
+ // the click event to force a select all instead.
+ maybeTripleClick : function(e)
+ {
+ if (!!this.tripleClickTimer) {
+ e.preventDefault();
+ this.app.searchBox.selectAllFacets();
+ return false;
+ }
+ },
+
+ // Is the user currently focused in the input field?
+ isFocused : function()
+ {
+ return this.box.is(':focus');
+ },
+
+ // When serializing the facets, the inputs need to also have their values
represented,
+ // in case they contain text that is not yet faceted (but will be once the search
is
+ // completed).
+ value : function()
+ {
+ return this.box.val();
+ },
+
+ // When switching between facets and inputs, depending on the direction the
cursor
+ // is coming from, the cursor in this facet's input field should match the
original
+ // direction.
+ setCursorAtEnd : function(direction)
+ {
+ if (direction == -1) {
+ this.box.setCursorPosition(this.box.val().length);
+ } else {
+ this.box.setCursorPosition(0);
+ }
+ },
+
+ // Selects the entire range of text in the input. Useful when tabbing between
inputs
+ // and facets.
+ selectText : function()
+ {
+ this.box.selectRange(0, this.box.val().length);
+ if (!this.app.searchBox.allSelected()) {
+ this.box.focus();
+ } else {
+ this.setMode('is', 'selected');
+ }
+ },
+
+ // Before the searchBox performs a search, we need to close the
+ // autocomplete menu.
+ search : function(e, direction)
+ {
+ if (!direction) direction = 0;
+ this.closeAutocomplete();
+ this.app.searchBox.searchEvent(e);
+ _.defer(_.bind(function()
+ {
+ this.app.searchBox.focusNextFacet(this, direction);
+ }, this));
+ },
+
+ // Callback fired on key press in the search box. We search when they hit
return.
+ keypress : function(e)
+ {
+ var key = VS.app.hotkeys.key(e);
+
+ if (key == 'enter') {
+ return this.search(e, 100);
+ } else if (VS.app.hotkeys.colon(e)) {
+ this.box.trigger('resize.autogrow', e);
+ var query = this.box.val();
+ var prefixes = this.options.callbacks.facetMatches() || [];
+ var labels = _.map(prefixes, function(prefix)
+ {
+ if (prefix.label) return prefix.label; else return
prefix;
+ });
+ if (_.contains(labels, query)) {
+ e.preventDefault();
+ var remainder = this.addTextFacetRemainder(query);
+ var position = this.options.position + (remainder ? 1 : 0);
+ this.app.searchBox.addFacet(query, '', position);
+ return false;
+ }
+ } else if (key == 'backspace') {
+ if (this.box.getCursorPosition() == 0 &&
!this.box.getSelection().length) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ this.app.searchBox.resizeFacets();
+ return false;
+ }
+ }
+ },
+
+ // Handles all keyboard inputs when in the input field. This checks
+ // for movement between facets and inputs, entering a new value that needs
+ // to be autocompleted, as well as stepping between facets with backspace.
+ keydown : function(e)
+ {
+ var key = VS.app.hotkeys.key(e);
+
+ if (key == 'left') {
+ if (this.box.getCursorPosition() == 0) {
+ e.preventDefault();
+ this.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
+ }
+ } else if (key == 'right') {
+ if (this.box.getCursorPosition() == this.box.val().length) {
+ e.preventDefault();
+ this.app.searchBox.focusNextFacet(this, 1, {selectFacet: true});
+ }
+ } else if (VS.app.hotkeys.shift && key == 'tab') {
+ e.preventDefault();
+ this.app.searchBox.focusNextFacet(this, -1, {selectText: true});
+ } else if (key == 'tab') {
+ e.preventDefault();
+ var value = this.box.val();
+ if (value.length) {
+ var remainder = this.addTextFacetRemainder(value);
+ var position = this.options.position + (remainder ? 1 : 0);
+ this.app.searchBox.addFacet(value, '', position);
+ } else {
+ this.app.searchBox.focusNextFacet(this, 0, {
+ skipToFacet: true,
+ selectText: true
+ });
+ }
+ } else if (VS.app.hotkeys.command &&
String.fromCharCode(e.which).toLowerCase() == 'a') {
+ e.preventDefault();
+ this.app.searchBox.selectAllFacets();
+ return false;
+ } else if (key == 'backspace' &&
!this.app.searchBox.allSelected()) {
+ if (this.box.getCursorPosition() == 0 &&
!this.box.getSelection().length) {
+ e.preventDefault();
+ this.app.searchBox.focusNextFacet(this, -1, {backspace: true});
+ return false;
+ }
+ }
+
+ this.box.trigger('resize.autogrow', e);
+ }
+
+ });
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // Makes the view enter a mode. Modes have both a 'mode' and a
'group',
+ // and are mutually exclusive with any other modes in the same group.
+ // Setting will update the view's modes hash, as well as set an HTML class
+ // of *[mode]_[group]* on the view's element. Convenient way to swap styles
+ // and behavior.
+ Backbone.View.prototype.setMode = function(mode, group)
+ {
+ this.modes || (this.modes = {});
+ if (this.modes[group] === mode) return;
+ $(this.el).setMode(mode, group);
+ this.modes[group] = mode;
+ };
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // DocumentCloud workspace hotkeys. To tell if a key is currently being pressed,
+ // just ask `VS.app.hotkeys.[key]` on `keypress`, or ask `VS.app.hotkeys.key(e)`
+ // on `keydown`.
+ //
+ // For the most headache-free way to use this utility, check modifier keys,
+ // like shift and command, with `VS.app.hotkeys.shift`, and check every other
+ // key with `VS.app.hotkeys.key(e) == 'key_name'`.
+ VS.app.hotkeys = {
+
+ // Keys that will be mapped to the `hotkeys` namespace.
+ KEYS: {
+ '16': 'shift',
+ '17': 'command',
+ '91': 'command',
+ '93': 'command',
+ '224': 'command',
+ '13': 'enter',
+ '37': 'left',
+ '38': 'upArrow',
+ '39': 'right',
+ '40': 'downArrow',
+ '46': 'delete',
+ '8': 'backspace',
+ '9': 'tab',
+ '188': 'comma'
+ },
+
+ // Binds global keydown and keyup events to listen for keys that match
`this.KEYS`.
+ initialize : function()
+ {
+ _.bindAll(this, 'down', 'up', 'blur');
+ $(document).bind('keydown', this.down);
+ $(document).bind('keyup', this.up);
+ $(window).bind('blur', this.blur);
+ },
+
+ // On `keydown`, turn on all keys that match.
+ down : function(e)
+ {
+ var key = this.KEYS[e.which];
+ if (key) this[key] = true;
+ },
+
+ // On `keyup`, turn off all keys that match.
+ up : function(e)
+ {
+ var key = this.KEYS[e.which];
+ if (key) this[key] = false;
+ },
+
+ // If an input is blurred, all keys need to be turned off, since they are no
longer
+ // able to modify the document.
+ blur : function(e)
+ {
+ for (var key in this.KEYS) this[this.KEYS[key]] = false;
+ },
+
+ // Check a key from an event and return the common english name.
+ key : function(e)
+ {
+ return this.KEYS[e.which];
+ },
+
+ // Colon is special, since the value is different between browsers.
+ colon : function(e)
+ {
+ var charCode = e.which;
+ return charCode && String.fromCharCode(charCode) == ":";
+ },
+
+ // Check a key from an event and match it against any known characters.
+ // The `keyCode` is different depending on the event type: `keydown` vs.
`keypress`.
+ //
+ // These were determined by looping through every `keyCode` and `charCode` that
+ // resulted from `keydown` and `keypress` events and counting what was
printable.
+ printable : function(e)
+ {
+ var code = e.which;
+ if (e.type == 'keydown') {
+ if (code == 32 || // space
+ (code >= 48 && code <= 90) || // 0-1a-z
+ (code >= 96 && code <= 111) || // 0-9+-/*.
+ (code >= 186 && code <= 192) || // ;=,-./^
+ (code >= 219 && code <= 222)) { // (\)'
+ return true;
+ }
+ } else {
+ // [space]!"#$%&'()*+,-.0-9:;<=>?@A-Z[\]^_`a-z{|} and
unicode characters
+ if ((code >= 32 && code <= 126) || (code >= 160
&& code <= 500) || (String.fromCharCode(code) == ":")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ };
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // Naive English transformations on words. Only used for a few transformations
+ // in VisualSearch.js.
+ VS.utils.inflector = {
+
+ // Delegate to the ECMA5 String.prototype.trim function, if available.
+ trim : function(s)
+ {
+ return s.trim ? s.trim() : s.replace(/^\s+|\s+$/g, '');
+ },
+
+ // Escape strings that are going to be used in a regex. Escapes punctuation
+ // that would be incorrect in a regex.
+ escapeRegExp : function(s)
+ {
+ return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
+ }
+ };
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ $.fn.extend({
+
+ // Makes the selector enter a mode. Modes have both a 'mode' and a
'group',
+ // and are mutually exclusive with any other modes in the same group.
+ // Setting will update the view's modes hash, as well as set an HTML class
+ // of *[mode]_[group]* on the view's element. Convenient way to swap styles
+ // and behavior.
+ setMode : function(state, group)
+ {
+ group = group || 'mode';
+ var re = new RegExp("\\w+_" + group + "(\\s|$)",
'g');
+ var mode = (state === null) ? "" : state + "_" + group;
+ this.each(function()
+ {
+ this.className = (this.className.replace(re, '') + ' ' +
mode).replace(/\s\s/g, ' ');
+ });
+ return mode;
+ },
+
+ // When attached to an input element, this will cause the width of the input
+ // to match its contents. This calculates the width of the contents of the input
+ // by measuring a hidden shadow div that should match the styling of the input.
+ autoGrowInput: function()
+ {
+ return this.each(function()
+ {
+ var $input = $(this);
+ var $tester = $('<div />').css({
+ opacity : 0,
+ top : -9999,
+ left : -9999,
+ position : 'absolute',
+ whiteSpace : 'nowrap'
+
}).addClass('VS-input-width-tester').addClass('VS-interface');
+
+ // Watch for input value changes on all of these events. `resize`
+ // event is called explicitly when the input has been changed without
+ // a single keypress.
+ var events = 'keydown.autogrow keypress.autogrow ' +
'resize.autogrow change.autogrow';
+ $input.next('.VS-input-width-tester').remove();
+ $input.after($tester);
+ $input.unbind(events).bind(events, function(e, realEvent)
+ {
+ if (realEvent) e = realEvent;
+ var value = $input.val();
+
+ // Watching for the backspace key is tricky because it may not
+ // actually be deleting the character, but instead the key gets
+ // redirected to move the cursor from facet to facet.
+ if (VS.app.hotkeys.key(e) == 'backspace') {
+ var position = $input.getCursorPosition();
+ if (position > 0) value = value.slice(0, position - 1) +
value.slice(position, value.length);
+ } else if (VS.app.hotkeys.printable(e) &&
!VS.app.hotkeys.command) {
+ value += String.fromCharCode(e.which);
+ }
+ value = value.replace(/&/g, '&').replace(/\s/g,
' ').replace(/</g, '<').replace(/>/g,
'>');
+
+ $tester.html(value);
+ $input.width($tester.width() + 3);
+ $input.trigger('updated.autogrow');
+ });
+
+ // Sets the width of the input on initialization.
+ $input.trigger('resize.autogrow');
+ });
+ },
+
+
+ // Cross-browser method used for calculating where the cursor is in an
+ // input field.
+ getCursorPosition: function()
+ {
+ var position = 0;
+ var input = this.get(0);
+
+ if (document.selection) { // IE
+ input.focus();
+ var sel = document.selection.createRange();
+ var selLen = document.selection.createRange().text.length;
+ sel.moveStart('character', -input.value.length);
+ position = sel.text.length - selLen;
+ } else if (input && $(input).is(':visible') &&
input.selectionStart != null) { // Firefox/Safari
+ position = input.selectionStart;
+ }
+
+ return position;
+ },
+
+ // A simple proxy for `selectRange` that sets the cursor position in an
+ // input field.
+ setCursorPosition: function(position)
+ {
+ return this.each(function()
+ {
+ return $(this).selectRange(position, position);
+ });
+ },
+
+ // Cross-browser way to select text in an input field.
+ selectRange: function(start, end)
+ {
+ return this.each(function()
+ {
+ if (this.setSelectionRange) { // FF/Webkit
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else if (this.createTextRange) { // IE
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', end);
+ range.moveStart('character', start);
+ if (end - start >= 0) range.select();
+ }
+ });
+ },
+
+ // Returns an object that contains the text selection range values for
+ // an input field.
+ getSelection: function()
+ {
+ var input = this[0];
+
+ if (input.selectionStart != null) { // FF/Webkit
+ var start = input.selectionStart;
+ var end = input.selectionEnd;
+ return {
+ start : start,
+ end : end,
+ length : end - start,
+ text : input.value.substr(start, end - start)
+ };
+ } else if (document.selection) { // IE
+ var range = document.selection.createRange();
+ if (range) {
+ var textRange = input.createTextRange();
+ var copyRange = textRange.duplicate();
+ textRange.moveToBookmark(range.getBookmark());
+ copyRange.setEndPoint('EndToStart', textRange);
+ var start = copyRange.text.length;
+ var end = start + range.text.length;
+ return {
+ start : start,
+ end : end,
+ length : end - start,
+ text : range.text
+ };
+ }
+ }
+ return {start: 0, end: 0, length: 0};
+ }
+
+ });
+
+ // Debugging in Internet Explorer. This allows you to use
+ // `console.log(['message', var1, var2, ...])`. Just remove the `false` and
+ // add your console.logs. This will automatically stringify objects using
+ // `JSON.stringify', so you can read what's going out. Think of this as a
+ // *Diet Firebug Lite Zero with Lemon*.
+ if ($.browser.msie && false) {
+ window.console = {};
+ var _$ied;
+ window.console.log = function(msg)
+ {
+ if (_.isArray(msg)) {
+ var message = msg[0];
+ var vars = _.map(msg.slice(1), function(arg)
+ {
+ return JSON.stringify(arg);
+ }).join(' - ');
+ }
+ if (!_$ied) {
+ _$ied = $('<div><ol></ol></div>').css({
+ 'position': 'fixed',
+ 'bottom': 10,
+ 'left': 10,
+ 'zIndex': 20000,
+ 'width': $('body').width() - 80,
+ 'border': '1px solid #000',
+ 'padding': '10px',
+ 'backgroundColor': '#fff',
+ 'fontFamily': 'arial,helvetica,sans-serif',
+ 'fontSize': '11px'
+ });
+ $('body').append(_$ied);
+ }
+ var $message = $('<li>' + message + ' - ' + vars +
'</li>').css({
+ 'borderBottom': '1px solid #999999'
+ });
+ _$ied.find('ol').append($message);
+ _.delay(function()
+ {
+ $message.fadeOut(500);
+ }, 5000);
+ };
+
+ }
+
+})();
+
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // Used to extract keywords and facets from the free text search.
+ VS.app.SearchParser = {
+
+ // Matches `category: "free text"`, with and without quotes.
+ ALL_FIELDS :
/('.+?'|".+?"|[^'"\s]{2}\S*):\s*('.+?'|".+?"|[^'"\s]\S*)/g,
+
+ // Matches a single category without the text. Used to correctly extract facets.
+ CATEGORY : /('.+?'|".+?"|[^'"\s]{2}\S*):\s*/,
+
+ // Called to parse a query into a collection of `SearchFacet` models.
+ parse : function(instance, query)
+ {
+ var searchFacets = this._extractAllFacets(instance, query);
+ instance.searchQuery.reset(searchFacets);
+ return searchFacets;
+ },
+
+ // Walks the query and extracts facets, categories, and free text.
+ _extractAllFacets : function(instance, query)
+ {
+ var facets = [];
+ var originalQuery = query;
+
+ while (query) {
+ var category, value;
+ originalQuery = query;
+ var field = this._extractNextField(query);
+ if (!field) {
+ category = 'text';
+ value = this._extractSearchText(query);
+ query = VS.utils.inflector.trim(query.replace(value, ''));
+ } else if (field.indexOf(':') != -1) {
+ category =
field.match(this.CATEGORY)[1].replace(/(^['"]|['"]$)/g, '');
+ value = field.replace(this.CATEGORY,
'').replace(/(^['"]|['"]$)/g, '');
+ query = VS.utils.inflector.trim(query.replace(field, ''));
+ } else if (field.indexOf(':') == -1) {
+ category = 'text';
+ value = field;
+ query = VS.utils.inflector.trim(query.replace(value, ''));
+ }
+
+ if (category && value) {
+ var searchFacet = new VS.model.SearchFacet({
+ category : category,
+ value : VS.utils.inflector.trim(value),
+ app : instance
+ });
+ facets.push(searchFacet);
+ }
+ if (originalQuery == query) break;
+ }
+
+ return facets;
+ },
+
+ // Extracts the first field found, capturing any free text that comes
+ // before the category.
+ _extractNextField : function(query)
+ {
+ var textRe =
/^\s*(\S+)\s+(?=\w+:\s?(('.+?'|".+?")|([^'"]{2}\S*)))/;
+ var textMatch = query.match(textRe);
+ if (textMatch && textMatch.length >= 1) {
+ return textMatch[1];
+ } else {
+ return this._extractFirstField(query);
+ }
+ },
+
+ // If there is no free text before the facet, extract the category and value.
+ _extractFirstField : function(query)
+ {
+ var fields = query.match(this.ALL_FIELDS);
+ return fields && fields.length && fields[0];
+ },
+
+ // If the found match is not a category and facet, extract the trimmed free
text.
+ _extractSearchText : function(query)
+ {
+ query = query || '';
+ var text = VS.utils.inflector.trim(query.replace(this.ALL_FIELDS,
''));
+ return text;
+ }
+
+ };
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // The model that holds individual search facets and their categories.
+ // Held in a collection by `VS.app.searchQuery`.
+ VS.model.SearchFacet = Backbone.Model.extend({
+
+ // Extract the category and value and serialize it in preparation for
+ // turning the entire searchBox into a search query that can be sent
+ // to the server for parsing and searching.
+ serialize : function()
+ {
+ var category = this.quoteCategory(this.get('category'));
+ var value = VS.utils.inflector.trim(this.get('value'));
+
+ if (!value) return '';
+
+ if (!_.contains(this.get("app").options.unquotable || [], category)
&& category != 'text') {
+ value = this.quoteValue(value);
+ }
+
+ if (category != 'text') {
+ category = category + ': ';
+ } else {
+ category = "";
+ }
+ return category + value;
+ },
+
+ // Wrap categories that have spaces or any kind of quote with opposite matching
+ // quotes to preserve the complex category during serialization.
+ quoteCategory : function(category)
+ {
+ var hasDoubleQuote = (/"/).test(category);
+ var hasSingleQuote = (/'/).test(category);
+ var hasSpace = (/\s/).test(category);
+
+ if (hasDoubleQuote && !hasSingleQuote) {
+ return "'" + category + "'";
+ } else if (hasSpace || (hasSingleQuote && !hasDoubleQuote)) {
+ return '"' + category + '"';
+ } else {
+ return category;
+ }
+ },
+
+ // Wrap values that have quotes in opposite matching quotes. If a value has
+ // both single and double quotes, just use the double quotes.
+ quoteValue : function(value)
+ {
+ var hasDoubleQuote = (/"/).test(value);
+ var hasSingleQuote = (/'/).test(value);
+
+ if (hasDoubleQuote && !hasSingleQuote) {
+ return "'" + value + "'";
+ } else {
+ return '"' + value + '"';
+ }
+ }
+
+ });
+
+})();
+(function()
+{
+
+ var $ = jQuery; // Handle namespaced jQuery
+
+ // Collection which holds all of the individual facets (category: value).
+ // Used for finding and removing specific facets.
+ VS.model.SearchQuery = Backbone.Collection.extend({
+
+ // Model holds the category and value of the facet.
+ model : VS.model.SearchFacet,
+
+ // Turns all of the facets into a single serialized string.
+ serialize : function()
+ {
+ return this.map(function(facet)
+ {
+ return facet.serialize();
+ }).join(' ');
+ },
+
+ facets : function()
+ {
+ return this.map(function(facet)
+ {
+ var value = {};
+ value[facet.get('category')] = facet.get('value');
+ return value;
+ });
+ },
+
+ // Find a facet by its category. Multiple facets with the same category
+ // is fine, but only the first is returned.
+ find : function(category)
+ {
+ var facet = this.detect(function(facet)
+ {
+ return facet.get('category') == category;
+ });
+ return facet && facet.get('value');
+ },
+
+ // Counts the number of times a specific category is in the search query.
+ count : function(category)
+ {
+ return this.select(function(facet)
+ {
+ return facet.get('category') == category;
+ }).length;
+ },
+
+ // Returns an array of extracted values from each facet in a category.
+ values : function(category)
+ {
+ var facets = this.select(function(facet)
+ {
+ return facet.get('category') == category;
+ });
+ return _.map(facets, function(facet)
+ {
+ return facet.get('value');
+ });
+ },
+
+ // Checks all facets for matches of either a category or both category and
value.
+ has : function(category, value)
+ {
+ return this.any(function(facet)
+ {
+ var categoryMatched = facet.get('category') == category;
+ if (!value) return categoryMatched;
+ return categoryMatched && facet.get('value') == value;
+ });
+ },
+
+ // Used to temporarily hide a specific category and serialize the search query.
+ withoutCategory : function(category)
+ {
+ return this.map(function(facet)
+ {
+ if (facet.get('category') != category) return facet.serialize();
+ }).join(' ');
+ }
+
+ });
+
+})();
+(function()
+{
+ window.JST = window.JST || {};
+
+ window.JST['search_box'] = _.template('<div
class="VS-search">\n <div class="VS-search-box-wrapper
VS-search-box">\n <div class="VS-icon
VS-icon-search"></div>\n <div
class="VS-search-inner"></div>\n <div class="VS-icon
VS-icon-cancel VS-cancel-search-box" title="clear search"></div>\n
</div>\n</div>');
+ window.JST['search_facet'] = _.template('<% if
(model.has(\'category\')) { %>\n <div class="category"><%=
model.get(\'category\') %>:</div>\n<% } %>\n\n<div
class="search_facet_input_container">\n <input type="text"
class="search_facet_input VS-interface" value=""
/>\n</div>\n\n<div class="search_facet_remove VS-icon
VS-icon-cancel"></div>');
+ window.JST['search_input'] = _.template('<input type="text"
/>');
+})();
\ No newline at end of file