From 9c17a38488fdb40a40a2fb414549229a128d2e95 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Tue, 23 Jan 2007 12:09:27 +0000 Subject: [PATCH] Merged DEV\EXTENSIONS to HEAD svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/DEV/EXTENSIONS@4865 svn://svn.alfresco.com:3691/alfresco/BRANCHES/DEV/EXTENSIONS@4866 . svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/DEV/EXTENSIONS@4872 svn://svn.alfresco.com:3691/alfresco/BRANCHES/DEV/EXTENSIONS@4884 . Dave and Gavin's search work git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4899 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/messages/webclient.properties | 6 + .../templates/api/SearchEngines_view_atom.ftl | 20 + .../templates/api/SearchEngines_view_html.ftl | 22 + .../templates/api/Services_view_html.ftl | 21 + ...Description_view_opensearchdescription.ftl | 11 + .../templates/api/TextSearch_query_.ftl | 15 + .../templates/api/TextSearch_view_atom.ftl | 40 + .../templates/api/TextSearch_view_html.ftl | 35 + .../templates/api/TextSearch_view_rss.ftl | 41 + .../alfresco/web-api-application-context.xml | 118 +- config/alfresco/web-api-config.xml | 43 + config/alfresco/web-client-config.xml | 3 + .../org/alfresco/web/api/APIContextAware.java | 27 + .../java/org/alfresco/web/api/APIRequest.java | 32 +- .../org/alfresco/web/api/APIResponse.java | 12 +- .../java/org/alfresco/web/api/APIService.java | 12 + .../java/org/alfresco/web/api/APIServlet.java | 2 +- .../web/api/APITemplateProcessor.java | 148 + .../java/org/alfresco/web/api/FormatMap.java | 75 + .../org/alfresco/web/api/FormatRegistry.java | 114 + .../web/api/TemplateClassPathSet.java | 98 + .../web/api/services/APIServiceImpl.java | 119 +- .../api/services/APIServiceTemplateImpl.java | 130 + .../web/api/services/SearchEngines.java | 241 + .../alfresco/web/api/services/Services.java | 125 + .../alfresco/web/api/services/TextSearch.java | 223 +- .../api/services/TextSearchDescription.java | 50 +- .../web/config/OpenSearchConfigElement.java | 186 + .../web/config/OpenSearchElementReader.java | 93 + .../web/ui/repo/component/UIOpenSearch.java | 240 + .../web/ui/repo/tag/OpenSearchTag.java | 43 + source/web/WEB-INF/faces-config-repo.xml | 5 + source/web/WEB-INF/repo.tld | 19 + source/web/css/opensearch.css | 83 + source/web/images/parts/os-background.gif | Bin 0 -> 1685 bytes source/web/jsp/sidebar/opensearch.jsp | 26 + source/web/scripts/ajax/common.js | 145 +- source/web/scripts/ajax/opensearch.js | 686 +++ .../ajax/yahoo/connection/connection-min.js | 201 +- .../ajax/yahoo/connection/connection.js | 1923 +++---- source/web/scripts/ajax/yahoo/dom/dom-min.js | 60 +- source/web/scripts/ajax/yahoo/dom/dom.js | 1773 +++---- .../web/scripts/ajax/yahoo/event/event-min.js | 70 +- source/web/scripts/ajax/yahoo/event/event.js | 3509 ++++++------- .../ajax/yahoo/treeview/assets/tree.css | 196 +- .../ajax/yahoo/treeview/treeview-min.js | 73 +- .../scripts/ajax/yahoo/treeview/treeview.js | 4415 +++++++++-------- .../scripts/ajax/yahoo/utilities/utilities.js | 478 +- .../yahoo/yahoo-dom-event/yahoo-dom-event.js | 127 +- .../web/scripts/ajax/yahoo/yahoo/yahoo-min.js | 13 +- source/web/scripts/ajax/yahoo/yahoo/yahoo.js | 288 +- 51 files changed, 9993 insertions(+), 6442 deletions(-) create mode 100644 config/alfresco/templates/api/SearchEngines_view_atom.ftl create mode 100644 config/alfresco/templates/api/SearchEngines_view_html.ftl create mode 100644 config/alfresco/templates/api/Services_view_html.ftl create mode 100644 config/alfresco/templates/api/TextSearchDescription_view_opensearchdescription.ftl create mode 100644 config/alfresco/templates/api/TextSearch_query_.ftl create mode 100644 config/alfresco/templates/api/TextSearch_view_atom.ftl create mode 100644 config/alfresco/templates/api/TextSearch_view_html.ftl create mode 100644 config/alfresco/templates/api/TextSearch_view_rss.ftl create mode 100644 config/alfresco/web-api-config.xml create mode 100644 source/java/org/alfresco/web/api/APITemplateProcessor.java create mode 100644 source/java/org/alfresco/web/api/FormatMap.java create mode 100644 source/java/org/alfresco/web/api/FormatRegistry.java create mode 100644 source/java/org/alfresco/web/api/TemplateClassPathSet.java create mode 100644 source/java/org/alfresco/web/api/services/APIServiceTemplateImpl.java create mode 100644 source/java/org/alfresco/web/api/services/SearchEngines.java create mode 100644 source/java/org/alfresco/web/api/services/Services.java create mode 100644 source/java/org/alfresco/web/config/OpenSearchConfigElement.java create mode 100644 source/java/org/alfresco/web/config/OpenSearchElementReader.java create mode 100644 source/java/org/alfresco/web/ui/repo/component/UIOpenSearch.java create mode 100644 source/java/org/alfresco/web/ui/repo/tag/OpenSearchTag.java create mode 100644 source/web/css/opensearch.css create mode 100644 source/web/images/parts/os-background.gif create mode 100644 source/web/jsp/sidebar/opensearch.jsp create mode 100644 source/web/scripts/ajax/opensearch.js diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 8f63fde456..a629c8f681 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -1296,6 +1296,12 @@ workflow_last_command=Last command: workflow_duration=Duration: workflow_duration_ms=ms +# OpenSearch messages +opensearch=OpenSearch +opensearch_desc=Provides ability to search across multiple OpenSearch supported search engines. +perform_search_in=Perform Search In +no_engines_registered=Failed to find any registered OpenSearch engines! + # UI Page Titles title_about=About Alfresco title_login=Alfresco Web Client - Login diff --git a/config/alfresco/templates/api/SearchEngines_view_atom.ftl b/config/alfresco/templates/api/SearchEngines_view_atom.ftl new file mode 100644 index 0000000000..ccf86b0781 --- /dev/null +++ b/config/alfresco/templates/api/SearchEngines_view_atom.ftl @@ -0,0 +1,20 @@ + + + Alfresco (${agent.edition}) + Registered Search Engines + ${xmldate(date)} + ${request.path}/images/logo/AlfrescoLogo16.ico + + ${request.authenticatedUsername!"unknown"} + + ${request.url?xml} +<#list engines as engine> + + ${engine.label} + + <#if engine.urlType == "description">OpenSearch Description<#else>Template URL - ${engine.type} + ${absurl(engine.url)?xml} + ${xmldate(date)} + + + \ No newline at end of file diff --git a/config/alfresco/templates/api/SearchEngines_view_html.ftl b/config/alfresco/templates/api/SearchEngines_view_html.ftl new file mode 100644 index 0000000000..4c94498466 --- /dev/null +++ b/config/alfresco/templates/api/SearchEngines_view_html.ftl @@ -0,0 +1,22 @@ + + + + + Search Engines registered with Alfresco +<#list engines as engine> +<#if engine.urlType == "description"> + + + + + +

Search Engines registered with Alfresco

+ + + \ No newline at end of file diff --git a/config/alfresco/templates/api/Services_view_html.ftl b/config/alfresco/templates/api/Services_view_html.ftl new file mode 100644 index 0000000000..ab473943e8 --- /dev/null +++ b/config/alfresco/templates/api/Services_view_html.ftl @@ -0,0 +1,21 @@ + + + + + Alfresco Web APIs + + +

Alfresco Web APIs

+ Alfresco ${agent.edition} v${agent.version}
+
+ ${services?size} services - Online documentation.
+
+ + +<#list services as service> + + + + +
NameMethodURLAuthenticationDefault Format
${service.name}${service.httpMethod}${service.httpUri}${service.requiredAuthentication}${service.defaultFormat}
  [${service.description}]
+ \ No newline at end of file diff --git a/config/alfresco/templates/api/TextSearchDescription_view_opensearchdescription.ftl b/config/alfresco/templates/api/TextSearchDescription_view_opensearchdescription.ftl new file mode 100644 index 0000000000..94e0ac450f --- /dev/null +++ b/config/alfresco/templates/api/TextSearchDescription_view_opensearchdescription.ftl @@ -0,0 +1,11 @@ + + + Alfresco Text Search + Alfresco ${agent.edition} Text Search ${agent.version} + Search Alfresco "company home" using text keywords + <#comment>IE takes first template from list, thus html response is listed first + + + + ${request.path}/images/logo/AlfrescoLogo16.ico + \ No newline at end of file diff --git a/config/alfresco/templates/api/TextSearch_query_.ftl b/config/alfresco/templates/api/TextSearch_query_.ftl new file mode 100644 index 0000000000..e1f5e51e2c --- /dev/null +++ b/config/alfresco/templates/api/TextSearch_query_.ftl @@ -0,0 +1,15 @@ +( + TYPE:"{http://www.alfresco.org/model/content/1.0}content" AND + ( + ( +<#list 1..terms?size as i> + @\\{http\\://www.alfresco.org/model/content/1.0\\}name:${terms[i - 1]} <#if (i < terms?size)> OR + + ) + ( +<#list 1..terms?size as i> + TEXT:${terms[i - 1]} <#if (i < terms?size)> OR + + ) + ) +) \ No newline at end of file diff --git a/config/alfresco/templates/api/TextSearch_view_atom.ftl b/config/alfresco/templates/api/TextSearch_view_atom.ftl new file mode 100644 index 0000000000..a73f1031db --- /dev/null +++ b/config/alfresco/templates/api/TextSearch_view_atom.ftl @@ -0,0 +1,40 @@ + + + Alfresco (${agent.edition}) + Alfresco Search: ${search.searchTerms} + ${xmldate(date)} + ${absurl("/images/logo/AlfrescoLogo16.ico")} + + ${request.authenticatedUsername!"unknown"} + + urn:uuid:${search.id} + ${search.totalResults} + ${search.startIndex} + ${search.itemsPerPage} + + + +<#if search.startPage > 1> + + + +<#if search.startPage < search.totalPages> + + + + +<#list search.results as row> + + ${row.name} + + ${absurl(row.icon16)} <#comment>TODO: What's the standard for entry icons? + urn:uuid:${row.id} + ${xmldate(row.properties.modified)} + ${row.properties.description!""} + + ${row.properties.creator} + + ${row.score} + + + \ No newline at end of file diff --git a/config/alfresco/templates/api/TextSearch_view_html.ftl b/config/alfresco/templates/api/TextSearch_view_html.ftl new file mode 100644 index 0000000000..37302cdac0 --- /dev/null +++ b/config/alfresco/templates/api/TextSearch_view_html.ftl @@ -0,0 +1,35 @@ + + + + + Alfresco Text Search: ${search.searchTerms} + + + + + + +

Alfresco Text Search

+ Results ${search.startIndex} - ${search.startIndex + search.totalPageItems - 1} of ${search.totalResults} for ${search.searchTerms} visible to user ${request.authenticatedUsername!"unknown"}. + + first +<#if search.startPage > 1> + previous + + ${search.startPage} +<#if search.startPage < search.totalPages> + next + + last + + \ No newline at end of file diff --git a/config/alfresco/templates/api/TextSearch_view_rss.ftl b/config/alfresco/templates/api/TextSearch_view_rss.ftl new file mode 100644 index 0000000000..9534c1d4e4 --- /dev/null +++ b/config/alfresco/templates/api/TextSearch_view_rss.ftl @@ -0,0 +1,41 @@ + + + + Alfresco Search: ${search.searchTerms} + ${request.servicePath}/search/text?q=${search.searchTerms?url}&p=${search.startPage}&c=${search.itemsPerPage}&l=${search.localeId}&guest=${request.guest?string("true","")}&format=${request.format} + Alfresco Search: ${search.searchTerms} + ${search.localeId} + ${xmldate(date)} + ${xmldate(date)} + Alfresco ${agent.edition} v${agent.version} + + Alfresco Search: ${search.searchTerms} + 16 + 16 + ${absurl("/images/logo/AlfrescoLogo16.ico")} + + ${search.totalResults} + ${search.startIndex} + ${search.itemsPerPage} + + +<#if search.startPage > 1> + + + +<#if search.startPage < search.totalPages> + + \ No newline at end of file diff --git a/config/alfresco/web-api-application-context.xml b/config/alfresco/web-api-application-context.xml index ce37fbfac9..798810447f 100644 --- a/config/alfresco/web-api-application-context.xml +++ b/config/alfresco/web-api-application-context.xml @@ -3,18 +3,76 @@ + + + + + + + + classpath:alfresco/web-api-config.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/html + text/xml + application/atom+xml + application/rss+xml + application/opensearchdescription+xml + + + + + + + MSIE + + + text/xml + text/xml + + + + + - + + + @@ -27,21 +85,67 @@ + + + + + + + + + + + + + + + + + alfresco/extension/templates/api + alfresco/templates/api + + + + + + + + + + + + + + + + + + + + + - - - - + + + - + + + + + + + + - + + diff --git a/config/alfresco/web-api-config.xml b/config/alfresco/web-api-config.xml new file mode 100644 index 0000000000..e70897c44f --- /dev/null +++ b/config/alfresco/web-api-config.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + /service/search/textsearchdescription.xml + + + /service/search/text?q={searchTerms}&p={startPage?}&c={count?}&l={language?}&guest={alf:guest?}&format=atom + + + /service/search/text?q={searchTerms}&p={startPage?}&c={count?}&l={language?}&guest={alf:guest?}&format=rss + + + /service/search/text?q={searchTerms}&p={startPage?}&c={count?}&l={language?}&guest={alf:guest?} + + + + + + + diff --git a/config/alfresco/web-client-config.xml b/config/alfresco/web-client-config.xml index d5ad96782d..a0351696ec 100644 --- a/config/alfresco/web-client-config.xml +++ b/config/alfresco/web-client-config.xml @@ -225,7 +225,10 @@ page="/jsp/sidebar/navigator.jsp" actions-config-id="navigator_actions" /> + + navigator diff --git a/source/java/org/alfresco/web/api/APIContextAware.java b/source/java/org/alfresco/web/api/APIContextAware.java index 354279b049..4bcead7bfc 100644 --- a/source/java/org/alfresco/web/api/APIContextAware.java +++ b/source/java/org/alfresco/web/api/APIContextAware.java @@ -1,9 +1,36 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ package org.alfresco.web.api; import javax.servlet.ServletContext; +/** + * Interface for marking that a service is API Context aware + * + * @author davidc + * + */ public interface APIContextAware { + /** + * Sets the API Context + * + * @param context api context + */ public void setAPIContext(ServletContext context); } diff --git a/source/java/org/alfresco/web/api/APIRequest.java b/source/java/org/alfresco/web/api/APIRequest.java index d9fa3c4e5e..6fe8a78444 100644 --- a/source/java/org/alfresco/web/api/APIRequest.java +++ b/source/java/org/alfresco/web/api/APIRequest.java @@ -89,6 +89,16 @@ public class APIRequest extends HttpServletRequestWrapper { return getPath() + getServletPath(); } + + /** + * Gets the full request URL + * + * @return request url e.g. http://localhost:port/alfresco/service/text?searchTerms=dsfsdf + */ + public String getUrl() + { + return getScheme() + "://" + getServerName() + ":" + getServerPort() + getPathInfo() + (getQueryString() != null ? "?" + getQueryString() : ""); + } /** * Gets the currently authenticated username @@ -120,5 +130,25 @@ public class APIRequest extends HttpServletRequestWrapper String format = getParameter("format"); return (format == null || format.length() == 0) ? "" : format; } - + + /** + * Get User Agent + * + * TODO: Expand on known agents + * + * @return MSIE / Firefox + */ + public String getAgent() + { + String userAgent = getHeader("user-agent"); + if (userAgent.indexOf("Firefox/") != -1) + { + return "Firefox"; + } + else if (userAgent.indexOf("MSIE") != -1) + { + return "MSIE"; + } + return null; + } } diff --git a/source/java/org/alfresco/web/api/APIResponse.java b/source/java/org/alfresco/web/api/APIResponse.java index e77c425388..d408acbbde 100644 --- a/source/java/org/alfresco/web/api/APIResponse.java +++ b/source/java/org/alfresco/web/api/APIResponse.java @@ -26,13 +26,13 @@ import javax.servlet.http.HttpServletResponseWrapper; */ public class APIResponse extends HttpServletResponseWrapper { - - // Content Types + // API Formats + public static final String HTML_FORMAT = "html"; + public static final String ATOM_FORMAT = "atom"; + public static final String RSS_FORMAT = "rss"; + public static final String XML_FORMAT = "xml"; + public static final String OPENSEARCH_DESCRIPTION_FORMAT = "opensearchdescription"; - public static final String HTML_TYPE = "text/html"; - public static final String OPEN_SEARCH_DESCRIPTION_TYPE = "application/opensearchdescription+xml"; - public static final String ATOM_TYPE = "application/atom+xml"; - public static final String XML_TYPE = "text/xml"; /** * Construct diff --git a/source/java/org/alfresco/web/api/APIService.java b/source/java/org/alfresco/web/api/APIService.java index 4d9ea4fbfe..22a72458cf 100644 --- a/source/java/org/alfresco/web/api/APIService.java +++ b/source/java/org/alfresco/web/api/APIService.java @@ -33,6 +33,11 @@ public interface APIService */ public String getName(); + /** + * Gets the description of this service + */ + public String getDescription(); + /** * Gets the required authentication level for execution of this service * @@ -54,6 +59,13 @@ public interface APIService */ public String getHttpUri(); + /** + * Gets the default response format + * + * @return response format + */ + public String getDefaultFormat(); + /** * Execute service * diff --git a/source/java/org/alfresco/web/api/APIServlet.java b/source/java/org/alfresco/web/api/APIServlet.java index 67dbd685ec..4c537df5cd 100644 --- a/source/java/org/alfresco/web/api/APIServlet.java +++ b/source/java/org/alfresco/web/api/APIServlet.java @@ -86,7 +86,7 @@ public class APIServlet extends BaseServlet String uri = request.getPathInfo(); if (logger.isDebugEnabled()) - logger.debug("Processing request (" + request.getHttpMethod() + ") " + request.getRequestURL() + (request.getQueryString() != null ? "?" + request.getQueryString() : "")); + logger.debug("Processing request (" + request.getHttpMethod() + ") " + request.getRequestURL() + (request.getQueryString() != null ? "?" + request.getQueryString() : "") + " (agent: " + request.getAgent() + ")"); APIService service = apiServiceRegistry.get(method, uri); if (service != null) diff --git a/source/java/org/alfresco/web/api/APITemplateProcessor.java b/source/java/org/alfresco/web/api/APITemplateProcessor.java new file mode 100644 index 0000000000..ec63523cd7 --- /dev/null +++ b/source/java/org/alfresco/web/api/APITemplateProcessor.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.api; + +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.repo.template.FreeMarkerProcessor; +import org.alfresco.repo.template.QNameAwareObjectWrapper; +import org.alfresco.util.AbstractLifecycleBean; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; + +import freemarker.cache.MruCacheStorage; +import freemarker.cache.TemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.TemplateExceptionHandler; + + +/** + * FreeMarker Processor for use in Web API + * + * Adds the ability to: + * - specify template loaders + * - caching of templates + * + * @author davidc + */ +public class APITemplateProcessor extends FreeMarkerProcessor implements ApplicationContextAware, ApplicationListener +{ + private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); + private Set templateLoaders = new HashSet(); + private String defaultEncoding; + private Configuration templateConfig; + + + /* (non-Javadoc) + * @see org.alfresco.repo.template.FreeMarkerProcessor#setDefaultEncoding(java.lang.String) + */ + public void setDefaultEncoding(String defaultEncoding) + { + this.defaultEncoding = defaultEncoding; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.template.FreeMarkerProcessor#getConfig() + */ + @Override + protected Configuration getConfig() + { + return templateConfig; + } + + /** + * Add a Template Loader + * + * @param templateLoader template loader + */ + public void addTemplateLoader(TemplateLoader templateLoader) + { + templateLoaders.add(templateLoader); + } + + /** + * Initialise FreeMarker Configuration + */ + protected void initConfig() + { + Configuration config = new Configuration(); + + // setup template cache + config.setCacheStorage(new MruCacheStorage(20, 100)); + + // setup template loaders + for (TemplateLoader templateLoader : templateLoaders) + { + config.setTemplateLoader(templateLoader); + } + + // use our custom object wrapper that can deal with QNameMap objects directly + config.setObjectWrapper(new QNameAwareObjectWrapper()); + + // rethrow any exception so we can deal with them + config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + // set template encoding + if (defaultEncoding != null) + { + config.setDefaultEncoding(defaultEncoding); + } + + // set output encoding + config.setOutputEncoding("UTF-8"); + + templateConfig = config; + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + lifecycle.setApplicationContext(applicationContext); + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(ApplicationEvent event) + { + lifecycle.onApplicationEvent(event); + } + + /** + * Hooks into Spring Application Lifecycle + */ + private class ProcessorLifecycle extends AbstractLifecycleBean + { + @Override + protected void onBootstrap(ApplicationEvent event) + { + initConfig(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } + } + +} diff --git a/source/java/org/alfresco/web/api/FormatMap.java b/source/java/org/alfresco/web/api/FormatMap.java new file mode 100644 index 0000000000..30f678f0b6 --- /dev/null +++ b/source/java/org/alfresco/web/api/FormatMap.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.api; + +import java.util.Map; + +import org.springframework.beans.factory.InitializingBean; + + +/** + * A map of mimetypes indexed by format. + * + * @author davidc + */ +public class FormatMap implements InitializingBean +{ + private FormatRegistry registry; + private String agent; + private Map formats; + + + /** + * Sets the Format Registry + * + * @param registry + */ + public void setRegistry(FormatRegistry registry) + { + this.registry = registry; + } + + /** + * Sets the User Agent for which the formats apply + * + * @param agent + */ + public void setAgent(String agent) + { + this.agent = agent; + } + + /** + * Sets the formats + * + * @param formats + */ + public void setFormats(Map formats) + { + this.formats = formats; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + // Add formats to format registry + registry.addFormats(agent, formats); + } + +} diff --git a/source/java/org/alfresco/web/api/FormatRegistry.java b/source/java/org/alfresco/web/api/FormatRegistry.java new file mode 100644 index 0000000000..af8d653b69 --- /dev/null +++ b/source/java/org/alfresco/web/api/FormatRegistry.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.api; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Maintains a registry of mimetypes (indexed by format and user agent) + * + * @author davidc + */ +public class FormatRegistry +{ + // Logger + private static final Log logger = LogFactory.getLog(FormatRegistry.class); + + private Map formats; + private Map> agentFormats; + + + /** + * Construct + */ + public FormatRegistry() + { + formats = new HashMap(); + agentFormats = new HashMap>(); + } + + /** + * Add formats + * + * @param agent + * @param formatsToAdd + */ + public void addFormats(String agent, Map formatsToAdd) + { + // retrieve formats list for agent + Map formatsForAgent = formats; + if (agent != null) + { + formatsForAgent = agentFormats.get(agent); + if (formatsForAgent == null) + { + formatsForAgent = new HashMap(); + agentFormats.put(agent, formatsForAgent); + } + } + + for (Map.Entry entry : formatsToAdd.entrySet()) + { + if (logger.isWarnEnabled()) + { + String mimetype = formatsForAgent.get(entry.getKey()); + if (mimetype != null) + { + logger.warn("Replacing mime type '" + mimetype + "' with '" + entry.getValue() + "' for API format '" + entry.getKey() + "' (agent: " + agent + ")"); + } + } + + formatsForAgent.put(entry.getKey(), entry.getValue()); + + if (logger.isDebugEnabled()) + logger.debug("Registered API format '" + entry.getKey() + "' with mime type '" + entry.getValue() + "' (agent: " + agent + ")"); + } + } + + /** + * Gets the mimetype for the specified user agent and format + * + * @param agent + * @param format + * @return mimetype (or null, if one is not registered) + */ + public String getMimeType(String agent, String format) + { + String mimetype = null; + + if (agent != null) + { + Map formatsForAgent = agentFormats.get(agent); + if (formatsForAgent != null) + { + mimetype = formatsForAgent.get(format); + } + } + + if (mimetype == null) + { + mimetype = formats.get(format); + } + + return mimetype; + } + +} diff --git a/source/java/org/alfresco/web/api/TemplateClassPathSet.java b/source/java/org/alfresco/web/api/TemplateClassPathSet.java new file mode 100644 index 0000000000..5bb340c557 --- /dev/null +++ b/source/java/org/alfresco/web/api/TemplateClassPathSet.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.api; + +import java.io.File; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import freemarker.cache.FileTemplateLoader; + + +/** + * A set of template class paths + * + * @author davidc + */ +public class TemplateClassPathSet implements InitializingBean +{ + // Logger + private static final Log logger = LogFactory.getLog(TemplateClassPathSet.class); + + private Set classPaths; + private APITemplateProcessor templateProcessor; + private ResourceLoader resourceLoader; + + /** + * Construct + */ + public TemplateClassPathSet() + { + resourceLoader = new DefaultResourceLoader(); + } + + /** + * Sets the Template Processor + * + * @param templateProcessor + */ + public void setTemplateProcessor(APITemplateProcessor templateProcessor) + { + this.templateProcessor = templateProcessor; + } + + /** + * Sets the paths + * + * @param classPaths + */ + public void setPaths(Set classPaths) + { + this.classPaths = classPaths; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + // Add class paths to template processor + for (String classPath : classPaths) + { + Resource resource = resourceLoader.getResource(classPath); + if (resource.exists()) + { + File file = resource.getFile(); + templateProcessor.addTemplateLoader(new FileTemplateLoader(file)); + + if (logger.isDebugEnabled()) + logger.debug("Registered template classpath '" + classPath); + } + else if (logger.isWarnEnabled()) + { + logger.warn("Template classpath '" + classPath + "' does not exist"); + } + } + } + +} diff --git a/source/java/org/alfresco/web/api/services/APIServiceImpl.java b/source/java/org/alfresco/web/api/services/APIServiceImpl.java index 62d2ece787..74bcb4c899 100644 --- a/source/java/org/alfresco/web/api/services/APIServiceImpl.java +++ b/source/java/org/alfresco/web/api/services/APIServiceImpl.java @@ -16,23 +16,27 @@ */ package org.alfresco.web.api.services; -import java.io.IOException; +import java.io.Writer; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletContext; +import org.alfresco.repo.template.AbsoluteUrlMethod; import org.alfresco.repo.template.ISO8601DateFormatMethod; import org.alfresco.repo.template.UrlEncodeMethod; import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.cmr.repository.TemplateProcessor; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.web.api.APIContextAware; import org.alfresco.web.api.APIRequest; import org.alfresco.web.api.APIResponse; import org.alfresco.web.api.APIService; +import org.alfresco.web.api.FormatRegistry; import org.springframework.beans.factory.BeanNameAware; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; /** @@ -49,7 +53,8 @@ public abstract class APIServiceImpl implements BeanNameAware, APIService, APICo private ServletContext context; private ServiceRegistry serviceRegistry; private DescriptorService descriptorService; - private TemplateService templateService; + private TemplateProcessor templateProcessor; + private FormatRegistry formatRegistry; // // Initialisation @@ -80,11 +85,11 @@ public abstract class APIServiceImpl implements BeanNameAware, APIService, APICo } /** - * @param templateService + * @param templateProcessor */ - public void setTemplateService(TemplateService templateService) + public void setTemplateProcessor(TemplateProcessor templateProcessor) { - this.templateService = templateService; + this.templateProcessor = templateProcessor; } /** @@ -95,6 +100,14 @@ public abstract class APIServiceImpl implements BeanNameAware, APIService, APICo this.descriptorService = descriptorService; } + /** + * @param formatRegistry + */ + public void setFormatRegistry(FormatRegistry formatRegistry) + { + this.formatRegistry = formatRegistry; + } + /** * Sets the Http URI * @@ -155,24 +168,39 @@ public abstract class APIServiceImpl implements BeanNameAware, APIService, APICo } /** - * @return templateService + * @return templateProcessor */ - protected TemplateService getTemplateService() + protected TemplateProcessor getTemplateProcessor() { - return templateService; + return templateProcessor; } /** - * Create a basic template model + * @return formatRegistry + */ + protected FormatRegistry getFormatRegistry() + { + return formatRegistry; + } + + + // + // Basic Templating Support + // + + + /** + * Create a basic API model * * @param req api request * @param res api response * @return template model */ - protected Map createTemplateModel(APIRequest req, APIResponse res) + protected Map createAPIModel(APIRequest req, APIResponse res) { Map model = new HashMap(7, 1.0f); model.put("xmldate", new ISO8601DateFormatMethod()); + model.put("absurl", new AbsoluteUrlMethod(req.getPath())); model.put("urlencode", new UrlEncodeMethod()); model.put("date", new Date()); model.put("agent", descriptorService.getServerDescriptor()); @@ -182,16 +210,71 @@ public abstract class APIServiceImpl implements BeanNameAware, APIService, APICo } /** - * Render a template to the API Response + * Render a template (identified by path) * - * @param template - * @param model - * @param res + * @param templatePath template path + * @param model model + * @param writer output writer */ - protected void renderTemplate(String template, Map model, APIResponse res) - throws IOException + protected void renderTemplate(String templatePath, Map model, Writer writer) { - templateService.processTemplateString(null, template, model, res.getWriter()); + templateProcessor.process(templatePath, model, writer); } + /** + * Render a template (contents as string) + * @param template the template + * @param model model + * @param writer output writer + */ + protected void renderString(String template, Map model, Writer writer) + { + templateProcessor.processString(template, model, writer); + } + + + /** + * Helper to retrieve API Service + * + * @param name name of service + * @return the service + */ + protected static APIService getMethod(String name) + { + String[] CONFIG_LOCATIONS = new String[] { "classpath:alfresco/application-context.xml", "classpath:alfresco/web-api-application-context.xml" }; + ApplicationContext context = new ClassPathXmlApplicationContext(CONFIG_LOCATIONS); + APIService method = (APIService)context.getBean(name); + return method; + } + + /** + * Create a base test model (for use stand-alone) + * + * @return test model + */ + protected Map createTestModel() + { + Map model = new HashMap(7, 1.0f); + + // create api methods + model.put("xmldate", new ISO8601DateFormatMethod()); + model.put("urlencode", new UrlEncodeMethod()); + model.put("absurl", new AbsoluteUrlMethod("http://test:8080/test")); + model.put("date", new Date()); + + // create dummy request model + Map request = new HashMap(); + request.put("servicePath", "http://localhost:8080/alfresco/service"); + request.put("path", "http://localhost:8080/alfresco"); + request.put("url", "http://localhost:8080/alfresco/service/testurl"); + request.put("guest", false); + request.put("format", "xml"); + model.put("request", request); + + // create dummy agent model + model.put("agent", getDescriptorService().getServerDescriptor()); + + return model; + } + } diff --git a/source/java/org/alfresco/web/api/services/APIServiceTemplateImpl.java b/source/java/org/alfresco/web/api/services/APIServiceTemplateImpl.java new file mode 100644 index 0000000000..cf763b1002 --- /dev/null +++ b/source/java/org/alfresco/web/api/services/APIServiceTemplateImpl.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.api.services; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Map; + +import org.alfresco.service.cmr.repository.TemplateException; +import org.alfresco.web.api.APIException; +import org.alfresco.web.api.APIRequest; +import org.alfresco.web.api.APIResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Template based implementation of an API Service + * + * @author davidc + */ +public abstract class APIServiceTemplateImpl extends APIServiceImpl +{ + // Logger + private static final Log logger = LogFactory.getLog(APIServiceTemplateImpl.class); + + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#execute(org.alfresco.web.api.APIRequest, org.alfresco.web.api.APIResponse) + */ + public void execute(APIRequest req, APIResponse res) throws IOException + { + // construct data model for template + Map model = createAPIModel(req, res); + model = createModel(req, res, model); + + // process requested format + String format = req.getFormat(); + if (format == null || format.length() == 0) + { + format = getDefaultFormat(); + } + String mimetype = getFormatRegistry().getMimeType(req.getAgent(), format); + if (mimetype == null) + { + throw new APIException("API format '" + format + "' does not exist"); + } + + // render output + res.setContentType(mimetype + ";charset=UTF-8"); + + if (logger.isDebugEnabled()) + logger.debug("Response content type: " + mimetype); + + try + { + renderTemplate(null, format, model, res.getWriter()); + } + catch(TemplateException e) + { + throw new APIException("Failed to process format '" + format + "'", e); + } + } + + + /** + * Create a custom service model + * + * @param req API request + * @param res API response + * @param model basic API model + * @return custom service model + */ + protected abstract Map createModel(APIRequest req, APIResponse res, Map model); + + + /** + * Render a template to the API Response + * + * @param type type of template (null defaults to type VIEW) + * @param format template format (null, default format) + * @param model data model to render + * @param writer where to output + */ + protected void renderTemplate(String type, String format, Map model, Writer writer) + { + type = (type == null) ? "view" : type; + format = (format == null) ? "" : format; + String templatePath = (this.getClass().getSimpleName() + "_" + type + "_" + format).replace(".", "/") + ".ftl"; + + if (logger.isDebugEnabled()) + logger.debug("Rendering service template '" + templatePath + "'"); + + renderTemplate(templatePath, model, writer); + } + + + /** + * Simple test that can be executed outside of web context + */ + /*package*/ void test(String format) + throws IOException + { + // create test model + Map model = createTestModel(); + + // render service template to string + StringWriter rendition = new StringWriter(); + PrintWriter writer = new PrintWriter(rendition); + renderTemplate(null, format, model, writer); + System.out.println(rendition.toString()); + } + +} diff --git a/source/java/org/alfresco/web/api/services/SearchEngines.java b/source/java/org/alfresco/web/api/services/SearchEngines.java new file mode 100644 index 0000000000..7c0be7226a --- /dev/null +++ b/source/java/org/alfresco/web/api/services/SearchEngines.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.api.services; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigService; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.web.api.APIRequest; +import org.alfresco.web.api.APIResponse; +import org.alfresco.web.api.APIRequest.HttpMethod; +import org.alfresco.web.api.APIRequest.RequiredAuthentication; +import org.alfresco.web.config.OpenSearchConfigElement; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * List of (server-side) registered Search Engines + * + * @author davidc + */ +public class SearchEngines extends APIServiceTemplateImpl +{ + // url argument values + public static final String URL_ARG_DESCRIPTION = "description"; + public static final String URL_ARG_TEMPLATE = "template"; + public static final String URL_ARG_ALL = "all"; + + // Logger + private static final Log logger = LogFactory.getLog(SearchEngines.class); + + // dependencies + protected ConfigService configService; + + /** + * @param configService + */ + public void setConfigService(ConfigService configService) + { + this.configService = configService; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getRequiredAuthentication() + */ + public RequiredAuthentication getRequiredAuthentication() + { + return APIRequest.RequiredAuthentication.None; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getHttpMethod() + */ + public HttpMethod getHttpMethod() + { + return APIRequest.HttpMethod.GET; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getDefaultFormat() + */ + public String getDefaultFormat() + { + return APIResponse.HTML_FORMAT; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getDescription() + */ + public String getDescription() + { + return "Retrieve a list of (server-side) registered search engines"; + } + + @Override + protected Map createModel(APIRequest req, APIResponse res, Map model) + { + String urlType = req.getParameter("type"); + if (urlType == null || urlType.length() == 0) + { + urlType = URL_ARG_DESCRIPTION; + } + else if (!urlType.equals(URL_ARG_DESCRIPTION) && !urlType.equals(URL_ARG_TEMPLATE) && !urlType.equals(URL_ARG_ALL)) + { + urlType = URL_ARG_DESCRIPTION; + } + + // + // retrieve open search engines configuration + // + + Set urls = getUrls(urlType); + model.put("urltype", urlType); + model.put("engines", urls); + return model; + } + + /** + * Retrieve registered search engines + * + * @return set of search engines + */ + private Set getUrls(String urlType) + { + if (logger.isDebugEnabled()) + logger.debug("Search Engine parameters: urltype=" + urlType); + + Set urls = new HashSet(); + Config config = configService.getConfig("OpenSearch"); + + OpenSearchConfigElement searchConfig = (OpenSearchConfigElement)config.getConfigElement(OpenSearchConfigElement.CONFIG_ELEMENT_ID); + for (OpenSearchConfigElement.EngineConfig engineConfig : searchConfig.getEngines()) + { + Map engineUrls = engineConfig.getUrls(); + for (Map.Entry engineUrl : engineUrls.entrySet()) + { + String type = engineUrl.getKey(); + String url = engineUrl.getValue(); + + if ((urlType.equals(URL_ARG_ALL)) || + (urlType.equals(URL_ARG_DESCRIPTION) && type.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION)) || + (urlType.equals(URL_ARG_TEMPLATE) && !type.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION))) + { + String label = engineConfig.getLabel(); + String labelId = engineConfig.getLabelId(); + if (labelId != null && labelId.length() > 0) + { + String i18nLabel = I18NUtil.getMessage(labelId); + label = (i18nLabel == null) ? "$$" + labelId + "$$" : i18nLabel; + } + urls.add(new UrlTemplate(label, type, url)); + } + + // TODO: Extract URL templates from OpenSearch description + else if (urlType.equals(URL_ARG_TEMPLATE) && + type.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION)) + { + } + } + } + + if (logger.isDebugEnabled()) + logger.debug("Retrieved " + urls.size() + " engine registrations"); + + return urls; + } + + /** + * Model object for representing a registered search engine + */ + public static class UrlTemplate + { + private String type; + private String label; + private String url; + private UrlTemplate engine; + + public UrlTemplate(String label, String type, String url) + { + this.label = label; + this.type = type; + this.url = url; + this.engine = null; + } + + public UrlTemplate(String label, String type, String url, UrlTemplate engine) + { + this(label, type, url); + this.engine = engine; + } + + public String getLabel() + { + return label; + } + + public String getType() + { + return type; + } + + public String getUrl() + { + return url; + } + + public String getUrlType() + { + return (type.equals(MimetypeMap.MIMETYPE_OPENSEARCH_DESCRIPTION) ? "description" : "template"); + } + + public UrlTemplate getEngine() + { + return engine; + } + } + + + /** + * Simple test that can be executed outside of web context + */ + public static void main(String[] args) + throws Exception + { + SearchEngines service = (SearchEngines)APIServiceImpl.getMethod("web.api.SearchEngines"); + service.test(APIResponse.ATOM_FORMAT); + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.services.APIServiceImpl#createTestModel() + */ + @Override + protected Map createTestModel() + { + Map model = super.createTestModel(); + Set urls = getUrls(URL_ARG_ALL); + model.put("urltype", "template"); + model.put("engines", urls); + return model; + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/web/api/services/Services.java b/source/java/org/alfresco/web/api/services/Services.java new file mode 100644 index 0000000000..8ec875e4cc --- /dev/null +++ b/source/java/org/alfresco/web/api/services/Services.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.api.services; + +import java.util.Collection; +import java.util.Map; + +import org.alfresco.web.api.APIRequest; +import org.alfresco.web.api.APIResponse; +import org.alfresco.web.api.APIService; +import org.alfresco.web.api.APIRequest.HttpMethod; +import org.alfresco.web.api.APIRequest.RequiredAuthentication; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + + +/** + * Retrieves the list of available Web APIs + * + * @author davidc + */ +public class Services extends APIServiceTemplateImpl implements ApplicationContextAware +{ + private ApplicationContext applicationContext; + + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getDescription() + */ + public String getDescription() + { + return "Retrieve the list of available Alfresco Web APIs"; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getRequiredAuthentication() + */ + public RequiredAuthentication getRequiredAuthentication() + { + return RequiredAuthentication.None; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getHttpMethod() + */ + public HttpMethod getHttpMethod() + { + return HttpMethod.GET; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getDefaultFormat() + */ + public String getDefaultFormat() + { + return APIResponse.HTML_FORMAT; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.services.APIServiceTemplateImpl#createModel(org.alfresco.web.api.APIRequest, org.alfresco.web.api.APIResponse, java.util.Map) + */ + @Override + protected Map createModel(APIRequest req, APIResponse res, Map model) + { + model.put("services", getServices()); + return model; + } + + /** + * Gets the collection of API Services + * + * @return api services + */ + private Collection getServices() + { + Map services = applicationContext.getBeansOfType(APIService.class, false, false); + return services.values(); + } + + + /** + * Simple test that can be executed outside of web context + */ + public static void main(String[] args) + throws Exception + { + Services service = (Services)APIServiceImpl.getMethod("web.api.Services"); + service.test(APIResponse.HTML_FORMAT); + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.services.APIServiceImpl#createTestModel() + */ + @Override + protected Map createTestModel() + { + Map model = super.createTestModel(); + model.put("services", getServices()); + return model; + } + +} diff --git a/source/java/org/alfresco/web/api/services/TextSearch.java b/source/java/org/alfresco/web/api/services/TextSearch.java index c8985abfb3..ca47df386e 100644 --- a/source/java/org/alfresco/web/api/services/TextSearch.java +++ b/source/java/org/alfresco/web/api/services/TextSearch.java @@ -16,29 +16,22 @@ */ package org.alfresco.web.api.services; -import java.io.IOException; -import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Date; +import java.io.Writer; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.alfresco.i18n.I18NUtil; -import org.alfresco.repo.template.ISO8601DateFormatMethod; -import org.alfresco.repo.template.UrlEncodeMethod; -import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TemplateImageResolver; import org.alfresco.service.cmr.repository.TemplateNode; -import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.descriptor.DescriptorService; -import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; +import org.alfresco.util.ParameterCheck; import org.alfresco.web.api.APIException; import org.alfresco.web.api.APIRequest; import org.alfresco.web.api.APIResponse; @@ -47,7 +40,6 @@ import org.alfresco.web.api.APIRequest.RequiredAuthentication; import org.alfresco.web.ui.common.Utils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationContext; /** @@ -55,7 +47,7 @@ import org.springframework.context.ApplicationContext; * * @author davidc */ -public class TextSearch extends APIServiceImpl +public class TextSearch extends APIServiceTemplateImpl { // Logger private static final Log logger = LogFactory.getLog(TextSearch.class); @@ -64,6 +56,7 @@ public class TextSearch extends APIServiceImpl // TODO: allow configuration of search store protected static final StoreRef SEARCH_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); protected static final int DEFAULT_ITEMS_PER_PAGE = 10; + protected static final String QUERY_TEMPLATE_TYPE = "query"; // dependencies protected SearchService searchService; @@ -102,16 +95,33 @@ public class TextSearch extends APIServiceImpl } /* (non-Javadoc) - * @see org.alfresco.web.api.APIService#execute(org.alfresco.web.api.APIRequest, org.alfresco.web.api.APIResponse) + * @see org.alfresco.web.api.APIService#getDefaultFormat() */ - public void execute(APIRequest req, APIResponse res) - throws IOException + public String getDefaultFormat() + { + return APIResponse.HTML_FORMAT; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getDescription() + */ + public String getDescription() + { + return "Issue an Alfresco Web Client keyword search"; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.services.APIServiceTemplateImpl#createModel(org.alfresco.web.api.APIRequest, org.alfresco.web.api.APIResponse, java.util.Map) + */ + @Override + protected Map createModel(APIRequest req, APIResponse res, Map model) { // - // process parameters + // process arguments // String searchTerms = req.getParameter("q"); + ParameterCheck.mandatoryString("q", searchTerms); String startPageArg = req.getParameter("p"); int startPage = 1; try @@ -147,31 +157,13 @@ public class TextSearch extends APIServiceImpl SearchResult results = search(searchTerms, startPage, itemsPerPage, locale); // - // render the results + // append to model // - String contentType = APIResponse.HTML_TYPE; - String template = HTML_TEMPLATE; - - // TODO: data-drive this - String format = req.getFormat(); - if (format.equals("atom")) - { - contentType = APIResponse.ATOM_TYPE; - template = ATOM_TEMPLATE; - } - else if (format.equals("xml")) - { - contentType = APIResponse.XML_TYPE; - template = ATOM_TEMPLATE; - } - - Map model = createTemplateModel(req, res); model.put("search", results); - res.setContentType(contentType + ";charset=UTF-8"); - renderTemplate(template, model, res); + return model; } - + /** * Execute the search * @@ -190,7 +182,9 @@ public class TextSearch extends APIServiceImpl String[] terms = searchTerms.split(" "); Map statementModel = new HashMap(7, 1.0f); statementModel.put("terms", terms); - String query = getTemplateService().processTemplateString(null, QUERY_STATEMENT, statementModel); + Writer queryWriter = new StringWriter(1024); + renderTemplate(QUERY_TEMPLATE_TYPE, null, statementModel, queryWriter); + String query = queryWriter.toString(); // execute query if (logger.isDebugEnabled()) @@ -408,162 +402,27 @@ public class TextSearch extends APIServiceImpl } } - - // TODO: place into accessible file - private final static String ATOM_TEMPLATE = - "\n" + - "\n" + - " Alfresco (${agent.edition})\n" + - " Alfresco Search: ${search.searchTerms}\n" + - " ${xmldate(date)}\n" + - " ${request.path}/images/logo/AlfrescoLogo16.ico\n" + - " \n" + - " <#if request.authenticatedUsername?exists>${request.authenticatedUsername}<#else>unknown\n" + - " \n" + - " urn:uuid:${search.id}\n" + - " ${search.totalResults}\n" + - " ${search.startIndex}\n" + - " ${search.itemsPerPage}\n" + - " \n" + - " \n" + - " \n" + - " \n" + - "<#if search.startPage > 1>" + - " \n" + - "" + - "<#if search.startPage < search.totalPages>" + - " \n" + - "" + - " \n" + - " \n" + - "<#list search.results as row>" + - " \n" + - " ${row.name}\n" + - " \n" + - " ${request.path}${row.icon16}\n" + // TODO: Standard for entry icons? - " urn:uuid:${row.id}\n" + - " ${xmldate(row.properties.modified)}\n" + - " ${row.properties.description}\n" + - " \n" + - " ${row.properties.creator}\n" + - " \n" + - " ${row.score}\n" + - " \n" + - "" + - ""; - // TODO: place into accessible file - private final static String HTML_TEMPLATE = - "\n" + - "\n" + - "\n" + - " \n" + - " Alfresco Text Search: ${search.searchTerms}\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "

Alfresco Text Search

\n" + - " Results ${search.startIndex} - ${search.startIndex + search.totalPageItems - 1} of ${search.totalResults} for ${search.searchTerms} " + - "visible to user <#if request.authenticatedUsername?exists>${request.authenticatedUsername}<#else>unknown.\n" + - "
    \n" + - "<#list search.results as row>" + - "
  • \n" + - " " + - " ${row.name}\n" + - "
    \n" + - " ${row.properties.description}\n" + - "
    \n" + - "
  • \n" + - "" + - "
\n" + - " first" + - "<#if search.startPage > 1>" + - " previous" + - "" + - " ${search.startPage}" + - "<#if search.startPage < search.totalPages>" + - " next" + - "" + - " last" + - " \n" + - "\n"; - - // TODO: place into accessible file - private final static String QUERY_STATEMENT = - "(" + - "TYPE:\"{http://www.alfresco.org/model/content/1.0}content\" AND " + - "(" + - "(" + - "<#list 1..terms?size as i>" + - "@\\{http\\://www.alfresco.org/model/content/1.0\\}name:${terms[i - 1]}" + - "<#if (i < terms?size)>" + - " OR " + - "" + - "" + - ") " + - "(" + - "<#list 1..terms?size as i>" + - "TEXT:${terms[i - 1]}" + - "<#if (i < terms?size)>" + - " OR " + - "" + - "" + - ")" + - ")" + - ")"; - - - /** * Simple test that can be executed outside of web context - * - * TODO: Move to test harness - * - * @param args - * @throws Exception */ public static void main(String[] args) throws Exception { - ApplicationContext context = ApplicationContextHelper.getApplicationContext(); - TextSearch method = new TextSearch(); - method.setServiceRegistry((ServiceRegistry)context.getBean(ServiceRegistry.SERVICE_REGISTRY)); - method.setTemplateService((TemplateService)context.getBean(ServiceRegistry.TEMPLATE_SERVICE.getLocalName())); - method.setSearchService((SearchService)context.getBean(ServiceRegistry.SEARCH_SERVICE.getLocalName())); - method.setDescriptorService((DescriptorService)context.getBean(ServiceRegistry.DESCRIPTOR_SERVICE.getLocalName())); - method.setHttpUri("/search/text"); - method.test(); + TextSearch service = (TextSearch)APIServiceImpl.getMethod("web.api.TextSearch"); + service.test(APIResponse.HTML_FORMAT); } - /** - * Simple test that can be executed outside of web context - * - * TODO: Move to test harness + /* (non-Javadoc) + * @see org.alfresco.web.api.services.APIServiceImpl#createTestModel() */ - private void test() + @Override + protected Map createTestModel() { + Map model = super.createTestModel(); SearchResult result = search("alfresco tutorial", 1, 5, I18NUtil.getLocale()); - - Map searchModel = new HashMap(7, 1.0f); - Map request = new HashMap(); - request.put("servicePath", "http://localhost:8080/alfresco/service"); - request.put("path", "http://localhost:8080/alfresco"); - request.put("guest", false); - request.put("format", "xml"); - searchModel.put("xmldate", new ISO8601DateFormatMethod()); - searchModel.put("urlencode", new UrlEncodeMethod()); - searchModel.put("date", new Date()); - searchModel.put("agent", getDescriptorService().getServerDescriptor()); - searchModel.put("request", request); - searchModel.put("search", result); - - StringWriter rendition = new StringWriter(); - PrintWriter writer = new PrintWriter(rendition); - getTemplateService().processTemplateString(null, ATOM_TEMPLATE, searchModel, writer); - System.out.println(rendition.toString()); + model.put("search", result); + return model; } - + } \ No newline at end of file diff --git a/source/java/org/alfresco/web/api/services/TextSearchDescription.java b/source/java/org/alfresco/web/api/services/TextSearchDescription.java index 2291819115..d480e87164 100644 --- a/source/java/org/alfresco/web/api/services/TextSearchDescription.java +++ b/source/java/org/alfresco/web/api/services/TextSearchDescription.java @@ -16,7 +16,6 @@ */ package org.alfresco.web.api.services; -import java.io.IOException; import java.util.Map; import org.alfresco.web.api.APIRequest; @@ -30,7 +29,7 @@ import org.alfresco.web.api.APIRequest.RequiredAuthentication; * * @author davidc */ -public class TextSearchDescription extends APIServiceImpl +public class TextSearchDescription extends APIServiceTemplateImpl { /* (non-Javadoc) @@ -50,27 +49,38 @@ public class TextSearchDescription extends APIServiceImpl } /* (non-Javadoc) - * @see org.alfresco.web.api.APIService#execute(org.alfresco.web.api.APIRequest, org.alfresco.web.api.APIResponse) + * @see org.alfresco.web.api.APIService#getDefaultFormat() */ - public void execute(APIRequest req, APIResponse res) - throws IOException + public String getDefaultFormat() { - Map model = createTemplateModel(req, res); - res.setContentType(APIResponse.OPEN_SEARCH_DESCRIPTION_TYPE + ";charset=UTF-8"); - renderTemplate(OPEN_SEARCH_DESCRIPTION, model, res); + return APIResponse.OPENSEARCH_DESCRIPTION_FORMAT; } - // TODO: place into accessible file - private final static String OPEN_SEARCH_DESCRIPTION = - "\n" + - "\n" + - " Alfresco Text Search\n" + - " Alfresco ${agent.edition} Text Search ${agent.version}\n" + - " Search Alfresco \"company home\" using text keywords\n" + - " \n" + - " \n" + - " \n" + - " ${request.path}/images/logo/AlfrescoLogo16.ico\n" + - ""; + /* (non-Javadoc) + * @see org.alfresco.web.api.services.APIServiceTemplateImpl#createModel(org.alfresco.web.api.APIRequest, org.alfresco.web.api.APIResponse, java.util.Map) + */ + @Override + protected Map createModel(APIRequest req, APIResponse res, Map model) + { + return model; + } + + /* (non-Javadoc) + * @see org.alfresco.web.api.APIService#getDescription() + */ + public String getDescription() + { + return "Retrieve the OpenSearch Description for the Alfresco Web Client keyword search"; + } + /** + * Simple test that can be executed outside of web context + */ + public static void main(String[] args) + throws Exception + { + TextSearchDescription service = (TextSearchDescription)APIServiceImpl.getMethod("web.api.TextSearchDescription"); + service.test(APIResponse.OPENSEARCH_DESCRIPTION_FORMAT); + } + } diff --git a/source/java/org/alfresco/web/config/OpenSearchConfigElement.java b/source/java/org/alfresco/web/config/OpenSearchConfigElement.java new file mode 100644 index 0000000000..1bcbb0a0a1 --- /dev/null +++ b/source/java/org/alfresco/web/config/OpenSearchConfigElement.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.element.ConfigElementAdapter; + + +/** + * Custom config element that represents the config data for open search + * + * @author davidc + */ +public class OpenSearchConfigElement extends ConfigElementAdapter +{ + public static final String CONFIG_ELEMENT_ID = "opensearch"; + + private Set engines = new HashSet(8, 10f); + + + /** + * Default constructor + */ + public OpenSearchConfigElement() + { + super(CONFIG_ELEMENT_ID); + } + + /** + * Constructor + * + * @param name Name of the element this config element represents + */ + public OpenSearchConfigElement(String name) + { + super(name); + } + + /** + * @see org.alfresco.config.ConfigElement#getChildren() + */ + public List getChildren() + { + throw new ConfigException("Reading the open search config via the generic interfaces is not supported"); + } + + /** + * @see org.alfresco.config.ConfigElement#combine(org.alfresco.config.ConfigElement) + */ + public ConfigElement combine(ConfigElement configElement) + { + OpenSearchConfigElement newElement = (OpenSearchConfigElement) configElement; + OpenSearchConfigElement combinedElement = new OpenSearchConfigElement(); + + // add all the plugins from this element + for (EngineConfig plugin : this.getEngines()) + { + combinedElement.addEngine(plugin); + } + + // add all the plugins from the given element + for (EngineConfig plugin : newElement.getEngines()) + { + combinedElement.addEngine(plugin); + } + + return combinedElement; + } + + /** + * @return Returns a set of the engines + */ + public Set getEngines() + { + return this.engines; + } + + /** + * Adds an engine + * + * @param pluginConfig A pre-configured engine config object + */ + /*package*/ void addEngine(EngineConfig engineConfig) + { + this.engines.add(engineConfig); + } + + + /** + * Inner class representing the configuration of an OpenSearch engine + * + * @author davidc + */ + public static class EngineConfig + { + protected String label; + protected String labelId; + protected Map urls = new HashMap(8, 10f); + + /** + * Construct + * + * @param label + * @param labelId + */ + public EngineConfig(String label, String labelId) + { + if ((label == null || label.length() == 0) && (labelId == null || labelId.length() == 0)) + { + throw new IllegalArgumentException("'label' or 'label-id' must be specified"); + } + this.label = label; + this.labelId = labelId; + } + + /** + * @return I18N label id + */ + public String getLabelId() + { + return labelId; + } + + /** + * @return label + */ + public String getLabel() + { + return label; + } + + /** + * Gets the urls supported by this engine + * + * @return urls + */ + public Map getUrls() + { + return urls; + } + + /** + * Adds a url + * + * @param pluginConfig A pre-configured plugin config object + */ + /*package*/ void addUrl(String mimetype, String uri) + { + this.urls.put(mimetype, uri); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" {label=").append(this.label); + buffer.append(" labelId=").append(this.labelId).append(")"); + return buffer.toString(); + } + } + +} diff --git a/source/java/org/alfresco/web/config/OpenSearchElementReader.java b/source/java/org/alfresco/web/config/OpenSearchElementReader.java new file mode 100644 index 0000000000..1bca407105 --- /dev/null +++ b/source/java/org/alfresco/web/config/OpenSearchElementReader.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.Iterator; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.alfresco.web.config.OpenSearchConfigElement.EngineConfig; +import org.dom4j.Element; + + +/** + * Custom element reader to parse config for the open search + * + * @author davidc + */ +public class OpenSearchElementReader implements ConfigElementReader +{ + public static final String ELEMENT_OPENSEARCH = "opensearch"; + public static final String ELEMENT_ENGINES = "engines"; + public static final String ELEMENT_ENGINE = "engine"; + public static final String ELEMENT_URL = "url"; + public static final String ATTR_TYPE = "type"; + public static final String ATTR_LABEL = "label"; + public static final String ATTR_LABEL_ID = "label-id"; + + + /** + * @see org.alfresco.config.xml.elementreader.ConfigElementReader#parse(org.dom4j.Element) + */ + @SuppressWarnings("unchecked") + public ConfigElement parse(Element element) + { + OpenSearchConfigElement configElement = null; + + if (element != null) + { + String elementName = element.getName(); + if (elementName.equals(ELEMENT_OPENSEARCH) == false) + { + throw new ConfigException("OpenSearchElementReader can only parse " + ELEMENT_OPENSEARCH + + "elements, the element passed was '" + elementName + "'"); + } + + // go through the registered engines + configElement = new OpenSearchConfigElement(); + Element pluginsElem = element.element(ELEMENT_ENGINES); + if (pluginsElem != null) + { + Iterator engines = pluginsElem.elementIterator(ELEMENT_ENGINE); + while(engines.hasNext()) + { + // construct engine + Element engineElem = engines.next(); + String label = engineElem.attributeValue(ATTR_LABEL); + String labelId = engineElem.attributeValue(ATTR_LABEL_ID); + EngineConfig engineCfg = new EngineConfig(label, labelId); + + // construct urls for engine + Iterator urlsConfig = engineElem.elementIterator(ELEMENT_URL); + while (urlsConfig.hasNext()) + { + Element urlConfig = urlsConfig.next(); + String type = urlConfig.attributeValue(ATTR_TYPE); + String url = urlConfig.getTextTrim(); + engineCfg.addUrl(type, url); + } + + // register engine config + configElement.addEngine(engineCfg); + } + } + } + + return configElement; + } +} diff --git a/source/java/org/alfresco/web/ui/repo/component/UIOpenSearch.java b/source/java/org/alfresco/web/ui/repo/component/UIOpenSearch.java new file mode 100644 index 0000000000..56bcad63c9 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/UIOpenSearch.java @@ -0,0 +1,240 @@ +package org.alfresco.web.ui.repo.component; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigService; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.web.app.Application; +import org.alfresco.web.config.OpenSearchConfigElement; +import org.alfresco.web.config.OpenSearchConfigElement.EngineConfig; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.SelfRenderingComponent; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * JSF component that provides an OpenSearch client, the engines + * searched are configured via the web api config. + * + * @author gavinc + */ +public class UIOpenSearch extends SelfRenderingComponent +{ + protected final static String SCRIPTS_WRITTEN = "_alfOpenSearchScripts"; + protected final static String ATOM_TYPE = "application/atom+xml"; + protected final static String ENGINE_ID_PREFIX = "eng"; + + // ------------------------------------------------------------------------------ + // Component Impl + + @Override + public String getFamily() + { + return "org.alfresco.faces.OpenSearch"; + } + + @Override + @SuppressWarnings("unchecked") + public void encodeBegin(FacesContext context) throws IOException + { + if (!isRendered()) return; + + ResponseWriter out = context.getResponseWriter(); + + List engines = getRegisteredEngines(context); + if (engines != null && engines.size() == 0) + { + out.write(Application.getMessage(context, "no_engines_registered")); + return; + } + + String clientId = this.getClientId(context); + + // output the scripts required by the component (checks are + // made to make sure the scripts are only written once) + Utils.writeYahooScripts(context, out, null); + + // write out the JavaScript specific to the OpenSearch component, + // again, make sure it's only done once + Object present = context.getExternalContext().getRequestMap().get(SCRIPTS_WRITTEN); + if (present == null) + { + out.write("\n"); + out.write("\n"); + + context.getExternalContext().getRequestMap().put(SCRIPTS_WRITTEN, Boolean.TRUE); + } + + // write out the javascript initialisation required + out.write("\n"); + + // write out the HTML + out.write("
\n"); + out.write(""); + out.write("
"); + out.write("
\n"); + out.write("
"); + out.write(""); + out.write(Application.getMessage(context, "items_per_page")); + out.write("
"); + out.write(Application.getMessage(context, "perform_search_in")); + out.write(":
"); + for (OpenSearchEngine engine : engines) + { + out.write(""); + } + out.write("
"); + out.write(""); + out.write(engine.getLabel()); + out.write("
\n"); + out.write("
\n
\n"); + } + + /** + * Returns a list of OpenSearchEngine objects representing the + * registered OpenSearch engines. + * + * @param context Faces context + * @return List of registered engines + */ + private List getRegisteredEngines(FacesContext context) + { + List engines = null; + + // get the web api config service object from spring + ConfigService cfgSvc = (ConfigService)FacesContextUtils. + getRequiredWebApplicationContext(context).getBean("web.api.Config"); + if (cfgSvc != null) + { + // get the OpenSearch configuration + Config cfg = cfgSvc.getConfig("OpenSearch"); + OpenSearchConfigElement osConfig = (OpenSearchConfigElement)cfg. + getConfigElement(OpenSearchConfigElement.CONFIG_ELEMENT_ID); + if (osConfig != null) + { + // generate the the list of engines with a unique for each + int id = 1; + engines = new ArrayList(); + + Set enginesCfg = osConfig.getEngines(); + for (EngineConfig engineCfg : enginesCfg) + { + // resolve engine label + String label = engineCfg.getLabel(); + String labelId = engineCfg.getLabelId(); + if (labelId != null && labelId.length() > 0) + { + label = Application.getMessage(context, labelId); + } + + // locate search engine template url of most appropriate response type + Map urls = engineCfg.getUrls(); + String url = urls.get(MimetypeMap.MIMETYPE_ATOM); + if (url == null) + { + url = urls.get(MimetypeMap.MIMETYPE_RSS); + } + + if (url != null) + { + if (url.startsWith("/")) + { + url = context.getExternalContext().getRequestContextPath() + url; + } + + // add the engine + OpenSearchEngine engine = new OpenSearchEngine(id, label, url); + engines.add(engine); + + // increase the id counter + id++; + } + } + } + } + + return engines; + } + + /** + * Inner class representing a registered OpenSearch engine. + */ + private class OpenSearchEngine + { + private String id; + private String label; + private String url; + + public OpenSearchEngine(int id, String label, String url) + { + this.id = ENGINE_ID_PREFIX + Integer.toString(id); + this.label = label; + this.url = url; + } + + public String getId() + { + return id; + } + + public String getLabel() + { + return label; + } + + public String getUrl() + { + return url; + } + } +} diff --git a/source/java/org/alfresco/web/ui/repo/tag/OpenSearchTag.java b/source/java/org/alfresco/web/ui/repo/tag/OpenSearchTag.java new file mode 100644 index 0000000000..37b4e2fcd7 --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/tag/OpenSearchTag.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.repo.tag; + +import org.alfresco.web.ui.common.tag.HtmlComponentTag; + +/** + * Tag class for the OpenSearch component + * + * @author gavinc + */ +public class OpenSearchTag extends HtmlComponentTag +{ + /** + * @see javax.faces.webapp.UIComponentTag#getComponentType() + */ + public String getComponentType() + { + return "org.alfresco.faces.OpenSearch"; + } + + /** + * @see javax.faces.webapp.UIComponentTag#getRendererType() + */ + public String getRendererType() + { + return null; + } +} diff --git a/source/web/WEB-INF/faces-config-repo.xml b/source/web/WEB-INF/faces-config-repo.xml index ec9a223624..ac93fe09ad 100644 --- a/source/web/WEB-INF/faces-config-repo.xml +++ b/source/web/WEB-INF/faces-config-repo.xml @@ -137,6 +137,11 @@ org.alfresco.faces.NodeInfo org.alfresco.web.ui.repo.component.UINodeInfo + + + + org.alfresco.faces.OpenSearch + org.alfresco.web.ui.repo.component.UIOpenSearch diff --git a/source/web/WEB-INF/repo.tld b/source/web/WEB-INF/repo.tld index 372ff18981..7efba8c526 100644 --- a/source/web/WEB-INF/repo.tld +++ b/source/web/WEB-INF/repo.tld @@ -1610,6 +1610,25 @@ + + openSearch + org.alfresco.web.ui.repo.tag.OpenSearchTag + JSP + + + The openSearch component renders a purely JavaScript based OpenSearch + client. Search engine registration is provided by the server during + initialisation, the client will search all the engines and render + the results. + + + + id + false + true + + + nodeWorkflowInfo org.alfresco.web.ui.repo.tag.NodeWorkflowInfoTag diff --git a/source/web/css/opensearch.css b/source/web/css/opensearch.css new file mode 100644 index 0000000000..12a0d35c2f --- /dev/null +++ b/source/web/css/opensearch.css @@ -0,0 +1,83 @@ +.osSidebarPanel +{ + width: 190px; +} + +.osPanel +{ + margin: 4px; +} + +.osOptions +{ + margin: 6px; + display: none; +} + +.osResults +{ + margin-top: 6px; + padding: 3px; + background: url(../images/parts/os-background.gif) 0 0 repeat-x; +} + +.osEngineTitle +{ + margin-bottom: 2px; + border-bottom: 1px dashed #bbb; +} + +.osEngineTitleText +{ + font-weight: bold; + color: #003366; + padding-bottom: 2px; +} + +.osResultsPosition +{ + font-size: 9px; + text-align: right; + padding-right: 2px; + _padding-right: 6px; +} + +.osResultsPaging +{ + font-size: 9px; + text-align: right; + padding: 4px; + padding-top: 6px; +} + +.osResultsPaging a, .osResultsPaging a:hover, .osResultsPaging a:link, .osResultsPaging a:visited +{ + font-size: 9px; +} + +.osResultNoMatch +{ + padding: 4px; +} + +.osResultIcon +{ + padding-right: 6px; + padding-top: 6px; +} + +.osResultTitle +{ + font-weight: bold; + padding-top: 4px; +} + +.osResultTitle a, .osResultTitle a:hover, .osResultTitle a:link, .osResultTitle a:visited +{ + font-weight: bold; +} + +.osResultSummary +{ + padding-bottom: 3px; +} \ No newline at end of file diff --git a/source/web/images/parts/os-background.gif b/source/web/images/parts/os-background.gif new file mode 100644 index 0000000000000000000000000000000000000000..a0b7b82b468e8167f462ad1d209f72a5e657b1df GIT binary patch literal 1685 zcmc(eiBHpK9L9ewuvUtOa@NYxi$f^IayTYiB50vZvT%(#C2ElwhEXRY>N4i6MGl2n zj;V3ZaAiTqW>KdG#T166lqoZI9R2V2m$Hu@Is4!5BjgNydP+@?L=pn|9 zARNYt38QfW0~Kp>a$;f<0YYIy4>4&1;V_xaCInp6%;qWd5auZmj;U9#rVwyZLz7^pB%%)WjN#z2LE;`Q7d7y}gsiaCn~jDZRRg~e(GW1wQ$ zY*wocW3_>B*z9(j&5nTz1BHEl9*h9tm|s|!pI^X0g@Iz>&6@=TT-1;j7r_{)FiTe6$Xm`Lwo{+{sKXD5hQAmm_-m|f=t~&7?gw?*^`>YqK}<8Ly0RX_=ZSP zxqCACSq5dV^n*3t31WqkG}z$vsfPa%iR@0HhZvd+CsIAA{!v>y*52&UH3#thUMSdZ zRh~@2cBQh(jY11b$oo_C)0V1h!AZ3igH%SPvo~Mx&`GvZ=-jmGCGXpUnr?26{LX%k zsKzJUb~&=1>@n(hoF1LulG=5~e|^59nDnJ+BJhInu3EeNz)LIKrLLx5RGAqYHvfD% zyDf40sY3Ehp}Tlw^}6Izv$CE$uqG(^pm{juUXnF5`Ixt6{l;E(SZt-6?08>KOFW_8!NgfC)ZBH0Eh zc@vxEm;EvOl<#L^w#YX-OVZ+-l~q6L7jQ)E5s+VyBJ%${tNv8=7kP(UPwgm{iu_~t zNksuMPlr7Mci%KW2;6gQR1~fG+a_2JBtJ%ee?lJ+hY^N`~3KIXxW-n4ZG6js$Q2cBPv2s-+RlpcKiK0Q6S z>@jJexlGNj3a*V~rnkxel@7GmzLHQvj~crMg1?`sr-Xi2t{LcPZ+Kf3dcr~yhqjTg zmxQ{cgpZnaTpOkClo_v1vnnh*sO$ESkOF2gq$&__k)x5@>(^2ist-`2Z zFX?SjS1w%FXs`Ac#7AA#cG|VSYx%V3Yn@}B{6G5jn&|6lA6m3Rol~aI+H!yvb5mV7 z#Ls<_Llflxoih}(^V__f4=tTo#4Y6>?=*p$1gRyfF#c?s2w+)UvB<3?s&KhLc@PUCk& zYyR!Ft2SPnbi*rtNa^a1f3*3DSAs6V?lsqw&UIRJ+vDpJ^;;Y7CH|8qTb=kQckk+r VPYdEd**KglTY2+*udg?e{2! +<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> +<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> +<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> +<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> + +
+ + + +
\ No newline at end of file diff --git a/source/web/scripts/ajax/common.js b/source/web/scripts/ajax/common.js index 71d314b7dc..eea0efedfb 100644 --- a/source/web/scripts/ajax/common.js +++ b/source/web/scripts/ajax/common.js @@ -1,5 +1,5 @@ // -// Alfresco AJAX support library +// Alfresco JavaScript support library // Gavin Cornwell 14-07-2006 // @@ -53,3 +53,146 @@ function getContextPath() return _alfContextPath; } + +/** + * Returns a single child element with the given tag + * name from the given parent. If more than one tag + * exists the first one is returned, if none exist null + * is returned. + */ +function getElementByTagName(elParent, tagName) +{ + var el = null; + + if (elParent != null && tagName != null) + { + var elems = elParent.getElementsByTagName(tagName); + if (elems != null && elems.length > 0) + { + el = elems[0]; + } + } + + return el; +} + +/** + * Returns a single child element with the given tag + * name and namespace from the given parent. + * If more than one tag exists the first one is returned, + * if none exist null is returned. + */ +function getElementByTagNameNS(elParent, nsUri, nsPrefix, tagName) +{ + var el = null; + + if (elParent != null && tagName != null) + { + var elems = null; + + if (elParent.getElementsByTagNameNS) + { + elems = elParent.getElementsByTagNameNS(nsUri, tagName); + } + else + { + elems = elParent.getElementsByTagName(nsPrefix + ":" + tagName); + } + + if (elems != null && elems.length > 0) + { + el = elems[0]; + } + } + + return el; +} + +/** + * Returns the text of the given DOM element object + */ +function getElementText(el) +{ + var txt = null; + + if (el.text != undefined) + { + // get text using IE specific property + txt = el.text; + } + else + { + // use the W3C textContent property + txt = el.textContent; + } + + return txt; +} + +/** + * Returns the text content of a single child element + * with the given tag name from the given parent. + * If more than one tag exists the text of the first one + * is returned, if none exist null is returned. + */ +function getElementTextByTagName(elParent, tagName) +{ + var txt = null; + + var el = getElementByTagName(elParent, tagName); + if (el != null) + { + txt = getElementText(el); + } + + return txt; +} + +/** + * Returns the text a single child element with the given tag + * name and namespace from the given parent. + * If more than one tag exists the text of the first one is returned, + * if none exist null is returned. + */ +function getElementTextByTagNameNS(elParent, nsUri, nsPrefix, tagName) +{ + var txt = null; + + var el = getElementByTagNameNS(elParent, nsUri, nsPrefix, tagName); + if (el != null) + { + txt = getElementText(el); + } + + return txt; +} + +/** + * Logs a message to a debug log window. + * + * Example taken from http://ajaxcookbook.org/javascript-debug-log + */ +function log(message) +{ + if (!log.window_ || log.window_.closed) + { + var win = window.open("", null, "width=400,height=200," + + "scrollbars=yes,resizable=yes,status=no," + + "location=no,menubar=no,toolbar=no"); + if (!win) return; + var doc = win.document; + doc.write("Debug Log" + + ""); + doc.close(); + log.window_ = win; + } + + var logLine = log.window_.document.createElement("div"); + logLine.appendChild(log.window_.document.createTextNode(message)); + log.window_.document.body.appendChild(logLine); +} + + + + + diff --git a/source/web/scripts/ajax/opensearch.js b/source/web/scripts/ajax/opensearch.js new file mode 100644 index 0000000000..fafd737c64 --- /dev/null +++ b/source/web/scripts/ajax/opensearch.js @@ -0,0 +1,686 @@ +// +// Alfresco OpenSearch library +// Gavin Cornwell 09-01-2007 +// +// NOTE: This script relies on common.js so therefore needs to be loaded +// prior to this one on the containing HTML page. + +var _OS_NS_PREFIX = "opensearch"; +var _OS_NS_URI = "http://a9.com/-/spec/opensearch/1.1/"; + +var _searchTermFieldId = null; +var _pageSizeFieldId = null; + +var _resultsDivId = "os-results"; +var _optionsDivId = "os-options"; +var _resultSetPanelId = "-osresults-panel"; +var _resultSetListId = "-osresults-list"; +var _resultSetPositionId = "-osresults-position"; +var _resultSetPagingId = "-osresults-paging"; + +var _engineEnabledId = "-engine-enabled"; +var _engines = []; +var _enginesById = []; + +/** + * Define an object to hold the definition of an OpenSearch engine + */ +function OpenSearchEngine(id, label, url) +{ + this.id = id; + this.label = label; + this.url = url; +} + +/** + * Sets the field id of the search term input control + */ +function setSearchTermFieldId(id) +{ + _searchTermFieldId = id; +} + +/** + * Sets the field id of the page size input control + */ +function setPageSizeFieldId(id) +{ + _pageSizeFieldId = id; +} + +/** + * Registers an OpenSearch engine to be called when performing queries + */ +function registerOpenSearchEngine(id, label, url) +{ + var se = new OpenSearchEngine(id, label, url); + _engines[_engines.length] = se; + _enginesById[id] = se; +} + + +/** + * Handles the key press event, if ENTER is pressed execute the query + */ +function handleKeyPress(e) +{ + var keycode; + + // get the keycode + if (window.event) + { + keycode = window.event.keyCode; + } + else if (e) + { + keycode = e.which; + } + + // if ENTER was pressed execute the query + if (keycode == 13) + { + executeQuery(); + return false; + } + + return true; +} + +/** + * Toggles the visibility of the options panel + */ +function toggleOptions(icon) +{ + var currentState = icon.className; + var optionsDiv = document.getElementById(_optionsDivId); + + if (currentState == "collapsed") + { + icon.src = getContextPath() + "/images/icons/expanded.gif"; + icon.className = "expanded"; + + // show the div holding the options + if (optionsDiv != null) + { + optionsDiv.style.display = "block"; + } + } + else + { + icon.src = getContextPath() + "/images/icons/collapsed.gif"; + icon.className = "collapsed"; + + // hide the div holding the options + if (optionsDiv != null) + { + optionsDiv.style.display = "none"; + } + } +} + +/** + * Executes the query against all the registered and selected opensearch engines + */ +function executeQuery() +{ + // gather the required parameters + var term = document.getElementById(_searchTermFieldId).value; + var count = document.getElementById(_pageSizeFieldId).value; + + // default the count if its invalid + if (count.length == 0 || isNaN(count)) + { + count = 5; + } + + // issue the queries if there is enough search criteria + if (term != null && term.length > 1) + { + // issue the search request for each enabled engine + for (var e = 0; e < _engines.length; e++) + { + // get the checkbox for the current engine + var ose = _engines[e]; + var engCheckbox = document.getElementById(ose.id + _engineEnabledId); + if (engCheckbox != null && engCheckbox.checked) + { + issueSearchRequest(ose, term, count); + } + } + } +} + +/** + * Issues an Ajax request for the given OpenSearchEngine + * using the given search term and page size. + */ +function issueSearchRequest(ose, term, pageSize) +{ + // generate the search url + var searchUrl = generateSearchUrl(ose.url, term, pageSize); + + // issue the request + if (searchUrl != null) + { + YAHOO.util.Connect.asyncRequest("GET", searchUrl, + { + success: processSearchResults, + failure: handleSearchError, + argument: [ose.id] + }, + null); + } + else + { + handleErrorYahoo("Failed to generate url for search engine '" + ose.label + + "'.\n\nThis is probably caused by missing required parameters, check the template url for the search engine."); + } +} + +/** + * Generates a concrete url for the given template url and parameters. + * + * All parameters (inside { and }) have to be replaced. We only need to populate + * the 'searchTerms' and 'count' parameters, all optional ones will use the + * empty string. If there is a mandatory parameter present (other than searchTerms + * and count) null will be returned. + */ +function generateSearchUrl(templateUrl, term, count) +{ + var searchUrl = null; + + // define regex pattern to look for params + var pattern = /\{+\w*\}+|\{+\w*\?\}+|\{+\w*:\w*\}+|\{+\w*:\w*\?\}+/g; + + var params = templateUrl.match(pattern); + if (params != null && params.length > 0) + { + searchUrl = templateUrl; + + // go through the parameters and replace the searchTerms and count + // parameters with the given values and then replace all optional + // parameters with an empty string. + for (var p = 0; p < params.length; p++) + { + var param = params[p]; + + if (param == "{searchTerms}") + { + searchUrl = searchUrl.replace(param, term); + } + else if (param == "{count}" || param == "{count?}") + { + searchUrl = searchUrl.replace(param, count); + } + else if (param.indexOf("?") != -1) + { + // replace the optional parameter with "" + searchUrl = searchUrl.replace(param, ""); + } + else + { + // an unknown manadatory parameter return + searchUrl = null; + break; + } + } + } + + return searchUrl; +} + +/** + * Processes the XML search results + */ +function processSearchResults(ajaxResponse) +{ + try + { + // render the results from the Ajax response + var engineId = ajaxResponse.argument[0]; + var feed = ajaxResponse.responseXML.documentElement; + + // if the name of the feed element is "rss", get the channel child element + if (feed.tagName == "rss") + { + feed = getElementByTagName(feed, "channel"); + } + + var resultsDiv = renderSearchResults(engineId, feed); + + // create the div to hold the results and add the results + var resultsPanel = document.getElementById(_resultsDivId); + if (resultsPanel != null) + { + // first remove any existing results + while (resultsPanel.firstChild) + { + resultsPanel.removeChild(resultsPanel.firstChild); + }; + + // add the new results + resultsPanel.appendChild(resultsDiv); + } + else + { + alert("Failed to final results panel, unable to render search results!"); + return; + } + } + catch (e) + { + handleError(e); + } +} + +/** + * Renders the results for the given feed element. + */ +function renderSearchResults(engineId, feed) +{ + // look up the label from the osengine registry + var engineLabel = _enginesById[engineId].label; + + // create the div to hold the results and the header bar + var sb = []; + sb[sb.length] = "
"; + sb[sb.length] = "
"; + sb[sb.length] = "
"; + sb[sb.length] = engineLabel; + sb[sb.length] = ""; + sb[sb.length] = generatePostionHTML(feed); + sb[sb.length] = "
"; + + // create the actual results to display, start with the containing div + sb[sb.length] = "
"; + sb[sb.length] = generateResultsListHTML(feed); + sb[sb.length] = "
"; + + // create the paging controls + sb[sb.length] = "
"; + sb[sb.length] = generatePagingHTML(engineId, feed); + sb[sb.length] = "
"; + + // close the containing div + sb[sb.length] = "
"; + + // create a div element to hold the results + var d = document.createElement("div"); + d.innerHTML = sb.join(""); + + // return the div + return d; +} + +/** + * Shows another page of the current search results for the + * given engineId + */ +function showPage(engineId, url) +{ + // execute the query and process the results + YAHOO.util.Connect.asyncRequest("GET", url, + { + success: processShowPageResults, + failure: handleSearchError, + argument: [engineId] + }, + null); +} + +/** + * Processes the search results and updates the postion, result list + * and paging controls. + */ +function processShowPageResults(ajaxResponse) +{ + try + { + // render the results from the Ajax response + var engineId = ajaxResponse.argument[0]; + var feed = ajaxResponse.responseXML.documentElement; + + // if the name of the feed element is "rss", get the channel child element + if (feed.tagName == "rss") + { + feed = getElementByTagName(feed, "channel"); + } + + // find the position div and update the count + var positionDiv = document.getElementById(engineId + _resultSetPositionId); + if (positionDiv != null) + { + positionDiv.innerHTML = generatePostionHTML(feed); + } + + // append the results list to the results list div + var resultsListDiv = document.getElementById(engineId + _resultSetListId); + if (resultsListDiv != null) + { + resultsListDiv.innerHTML = generateResultsListHTML(feed); + } + + // update the paging div with new urls + var pagingDiv = document.getElementById(engineId + _resultSetPagingId); + if (pagingDiv != null) + { + pagingDiv.innerHTML = generatePagingHTML(engineId, feed); + } + } + catch (e) + { + handleError(e); + } +} + +/** + * Generates the HTML required to display the current position i.e. "x - y of z". + */ +function generatePostionHTML(feed) +{ + var totalResults = 0; + var pageSize = 5; + var startIndex = 0; + + // extract position information from results + var elTotalResults = getElementByTagNameNS(feed, _OS_NS_URI, _OS_NS_PREFIX, "totalResults"); + if (elTotalResults != null) + { + totalResults = getElementText(elTotalResults); + } + + // if there are no results just return an empty string + if (totalResults == 0) + { + return ""; + } + + var elStartIndex = getElementByTagNameNS(feed, _OS_NS_URI, _OS_NS_PREFIX, "startIndex"); + if (elStartIndex != null) + { + startIndex = getElementText(elStartIndex); + } + + var elItemsPerPage = getElementByTagNameNS(feed, _OS_NS_URI, _OS_NS_PREFIX, "itemsPerPage"); + if (elItemsPerPage != null) + { + pageSize = getElementText(elItemsPerPage); + } + + // calculate the number of pages the results span + /*var noPages = Math.floor(totalResults / pageSize); + var remainder = totalResults % pageSize; + if (remainder != 0) + { + noPages++; + }*/ + + // calculate the endIndex for this set of results + var endIndex = (Number(startIndex) + Number(pageSize)) - 1; + if (endIndex > totalResults) + { + endIndex = totalResults; + } + + // make sure the startIndex is correct + if (totalResults == 0) + { + startIndex = 0; + } + + var sb = []; + sb[sb.length] = startIndex; + sb[sb.length] = " - "; + sb[sb.length] = endIndex; + sb[sb.length] = " of "; + sb[sb.length] = totalResults; + + return sb.join(""); +} + +/** + * Generates the HTML to display the search results from the + * given feed. + */ +function generateResultsListHTML(feed) +{ + var isAtom = true; + + // if the name of the feed element is "channel" this is an RSS feed + if (feed.tagName == "channel") + { + isAtom = false; + } + + var results = null; + if (isAtom) + { + results = feed.getElementsByTagName("entry"); + } + else + { + results = feed.getElementsByTagName("item"); + } + + if (results == null || results.length == 0) + { + return "
No results
"; + } + else + { + var sb = []; + sb[sb.length] = ""; + + for (var x = 0; x < results.length; x++) + { + // get the current entry + var elResult = results[x]; + + // get the title, icon and summary + var title = getElementTextByTagName(elResult, "title"); + var icon = getElementTextByTagName(elResult, "icon"); + var summary = null; + if (isAtom) + { + summary = getElementTextByTagName(elResult, "summary"); + } + else + { + summary = getElementTextByTagName(elResult, "description"); + } + + // get the link href + var link = null; + var elLink = getElementByTagName(elResult, "link"); + if (elLink != null) + { + if (isAtom) + { + link = elLink.getAttribute("href"); + } + else + { + link = getElementText(elLink); + } + } + + // generate the row to represent the result + sb[sb.length] = ""; + } + + // close the table + sb[sb.length] = "
"; + if (icon != null) + { + sb[sb.length] = ""; + } + sb[sb.length] = "
"; + if (title != null) + { + if (link != null) + { + sb[sb.length] = ""; + } + sb[sb.length] = title; + if (link != null) + { + sb[sb.length] = ""; + } + } + sb[sb.length] = "
"; + if (summary != null) + { + sb[sb.length] = summary; + } + sb[sb.length] = "
"; + + return sb.join(""); + } +} + +/** + * Generates the HTML to display the paging links i.e. first, next, previous and last. + */ +function generatePagingHTML(engineId, feed) +{ + // check there are results + var totalResults = 0; + var elTotalResults = getElementByTagNameNS(feed, _OS_NS_URI, _OS_NS_PREFIX, "totalResults"); + if (elTotalResults != null) + { + totalResults = getElementText(elTotalResults); + } + + // if there are no results return an empty string + if (totalResults == 0) + { + return ""; + } + + // extract the navigation urls + var firstUrl = null; + var nextUrl = null; + var previousUrl = null; + var lastUrl = null; + + var links = feed.getElementsByTagName("link"); + if (links != null && links.length > 0) + { + for (var x = 0; x < links.length; x++) + { + var elNavLink = links[x]; + var linkType = elNavLink.getAttribute("rel"); + if (linkType == "first") + { + firstUrl = elNavLink.getAttribute("href"); + } + else if (linkType == "next") + { + nextUrl = elNavLink.getAttribute("href"); + } + else if (linkType == "previous") + { + previousUrl = elNavLink.getAttribute("href"); + } + else if (linkType == "last") + { + lastUrl = elNavLink.getAttribute("href"); + } + } + } + + var sb = []; + + if (firstUrl != null) + { + sb[sb.length] = "first | "; + } + if (previousUrl != null) + { + sb[sb.length] = "previous"; + if (nextUrl != null) + { + sb[sb.length] = " | "; + } + } + if (nextUrl != null) + { + sb[sb.length] = "next | "; + } + if (lastUrl != null) + { + sb[sb.length] = "last"; + } + + return sb.join(""); +} + +/** + * Error handler for errors caught in a catch block + */ +function handleError(o) +{ + var msg = null; + + if (e.message) + { + msg = e.message; + } + else + { + msg = e; + } + + alert("Error occurred processing search results: " + msg); +} + +/** + * Error handler for Ajax call to search engine + */ +function handleSearchError(o) +{ + // TODO: find out which search engine results could not be found! + + handleErrorYahoo("Error: Failed to retrieve search results"); +} + +/** + * Error handler for Ajax call to initialise component + */ +function handleInitError(o) +{ + handleErrorYahoo("Failed to initialise OpenSearch component: " + + o.status + " " + o.statusText); +} diff --git a/source/web/scripts/ajax/yahoo/connection/connection-min.js b/source/web/scripts/ajax/yahoo/connection/connection-min.js index 95d39ac8b3..7d44fbac29 100644 --- a/source/web/scripts/ajax/yahoo/connection/connection-min.js +++ b/source/web/scripts/ajax/yahoo/connection/connection-min.js @@ -1,99 +1,104 @@ -/* Copyright (c) 2006, Yahoo! Inc. All rights reserved.Code licensed under the BSD License:http://developer.yahoo.net/yui/license.txt version: 0.12.0 */ -YAHOO.util.Connect={_msxml_progid:['MSXML2.XMLHTTP.3.0','MSXML2.XMLHTTP','Microsoft.XMLHTTP'],_http_header:{},_has_http_headers:false,_use_default_post_header:true,_default_post_header:'application/x-www-form-urlencoded',_isFormSubmit:false,_isFileUpload:false,_formNode:null,_sFormData:null,_poll:{},_timeOut:{},_polling_interval:50,_transaction_id:0,setProgId:function(id) -{this._msxml_progid.unshift(id);},setDefaultPostHeader:function(b) -{this._use_default_post_header=b;},setPollingInterval:function(i) -{if(typeof i=='number'&&isFinite(i)){this._polling_interval=i;}},createXhrObject:function(transactionId) -{var obj,http;try -{http=new XMLHttpRequest();obj={conn:http,tId:transactionId};} -catch(e) -{for(var i=0;i=200&&httpStatus<300){try -{responseObject=this.createResponseObject(o,callback.argument);if(callback.success){if(!callback.scope){callback.success(responseObject);} -else{callback.success.apply(callback.scope,[responseObject]);}}} -catch(e){}} -else{try -{switch(httpStatus){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:responseObject=this.createExceptionObject(o.tId,callback.argument,(isAbort?isAbort:false));if(callback.failure){if(!callback.scope){callback.failure(responseObject);} -else{callback.failure.apply(callback.scope,[responseObject]);}} -break;default:responseObject=this.createResponseObject(o,callback.argument);if(callback.failure){if(!callback.scope){callback.failure(responseObject);} -else{callback.failure.apply(callback.scope,[responseObject]);}}}} -catch(e){}} -this.releaseObject(o);responseObject=null;},createResponseObject:function(o,callbackArg) -{var obj={};var headerObj={};try -{var headerStr=o.conn.getAllResponseHeaders();var header=headerStr.split('\n');for(var i=0;i');if(typeof secureUri=='boolean'){io.src='javascript:false';} -else if(typeof secureURI=='string'){io.src=secureUri;}} -else{var io=document.createElement('iframe');io.id=frameId;io.name=frameId;} -io.style.position='absolute';io.style.top='-1000px';io.style.left='-1000px';document.body.appendChild(io);},appendPostData:function(postData) -{var formElements=new Array();var postMessage=postData.split('&');for(var i=0;i0){try -{for(var i=0;i=200&&httpStatus<300){try +{responseObject=this.createResponseObject(o,callback.argument);if(callback.success){if(!callback.scope){callback.success(responseObject);} +else{callback.success.apply(callback.scope,[responseObject]);}}} +catch(e){}} +else{try +{switch(httpStatus){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:responseObject=this.createExceptionObject(o.tId,callback.argument,(isAbort?isAbort:false));if(callback.failure){if(!callback.scope){callback.failure(responseObject);} +else{callback.failure.apply(callback.scope,[responseObject]);}} +break;default:responseObject=this.createResponseObject(o,callback.argument);if(callback.failure){if(!callback.scope){callback.failure(responseObject);} +else{callback.failure.apply(callback.scope,[responseObject]);}}}} +catch(e){}} +this.releaseObject(o);responseObject=null;},createResponseObject:function(o,callbackArg) +{var obj={};var headerObj={};try +{var headerStr=o.conn.getAllResponseHeaders();var header=headerStr.split('\n');for(var i=0;i');if(typeof secureUri=='boolean'){io.src='javascript:false';} +else if(typeof secureURI=='string'){io.src=secureUri;}} +else{var io=document.createElement('iframe');io.id=frameId;io.name=frameId;} +io.style.position='absolute';io.style.top='-1000px';io.style.left='-1000px';document.body.appendChild(io);},appendPostData:function(postData) +{var formElements=[];var postMessage=postData.split('&');for(var i=0;i0){try +{for(var i=0;i= 200 && httpStatus < 300){ - try - { - responseObject = this.createResponseObject(o, callback.argument); - if(callback.success){ - if(!callback.scope){ - callback.success(responseObject); - } - else{ - // If a scope property is defined, the callback will be fired from - // the context of the object. - callback.success.apply(callback.scope, [responseObject]); - } - } - } - catch(e){} - } - else{ - try - { - switch(httpStatus){ - // The following cases are wininet.dll error codes that may be encountered. - case 12002: // Server timeout - case 12029: // 12029 to 12031 correspond to dropped connections. - case 12030: - case 12031: - case 12152: // Connection closed by server. - case 13030: // See above comments for variable status. - responseObject = this.createExceptionObject(o.tId, callback.argument, (isAbort?isAbort:false)); - if(callback.failure){ - if(!callback.scope){ - callback.failure(responseObject); - } - else{ - callback.failure.apply(callback.scope, [responseObject]); - } - } - break; - default: - responseObject = this.createResponseObject(o, callback.argument); - if(callback.failure){ - if(!callback.scope){ - callback.failure(responseObject); - } - else{ - callback.failure.apply(callback.scope, [responseObject]); - } - } - } - } - catch(e){} - } - - this.releaseObject(o); - responseObject = null; - }, - - /** - * @description This method evaluates the server response, creates and returns the results via - * its properties. Success and failure cases will differ in the response - * object's property values. - * @method createResponseObject - * @private - * @static - * @param {object} o The connection object - * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback - * @return {object} - */ - createResponseObject:function(o, callbackArg) - { - var obj = {}; - var headerObj = {}; - - try - { - var headerStr = o.conn.getAllResponseHeaders(); - var header = headerStr.split('\n'); - for(var i=0; i'); - - // IE will throw a security exception in an SSL environment if the - // iframe source is undefined. - if(typeof secureUri == 'boolean'){ - io.src = 'javascript:false'; - } - else if(typeof secureURI == 'string'){ - // Deprecated - io.src = secureUri; - } - } - else{ - var io = document.createElement('iframe'); - io.id = frameId; - io.name = frameId; - } - - io.style.position = 'absolute'; - io.style.top = '-1000px'; - io.style.left = '-1000px'; - - document.body.appendChild(io); - }, - - /** - * @description Parses the POST data and creates hidden form elements - * for each key-value, and appends them to the HTML form object. - * @method appendPostData - * @private - * @static - * @param {string} postData The HTTP POST data - * @return {array} formElements Collection of hidden fields. - */ - appendPostData:function(postData) - { - var formElements = new Array(); - var postMessage = postData.split('&'); - for(var i=0; i < postMessage.length; i++){ - var delimitPos = postMessage[i].indexOf('='); - if(delimitPos != -1){ - formElements[i] = document.createElement('input'); - formElements[i].type = 'hidden'; - formElements[i].name = postMessage[i].substring(0,delimitPos); - formElements[i].value = postMessage[i].substring(delimitPos+1); - this._formNode.appendChild(formElements[i]); - } - } - - return formElements; - }, - - /** - * @description Uploads HTML form, including files/attachments, to the - * iframe created in createFrame. - * @method uploadFile - * @private - * @static - * @param {int} id The transaction id. - * @param {object} callback - User-defined callback object. - * @param {string} uri Fully qualified path of resource. - * @return {void} - */ - uploadFile:function(id, callback, uri, postData){ - - // Each iframe has an id prefix of "yuiIO" followed - // by the unique transaction id. - var frameId = 'yuiIO' + id; - var io = document.getElementById(frameId); - - // Initialize the HTML form properties in case they are - // not defined in the HTML form. - this._formNode.action = uri; - this._formNode.method = 'POST'; - this._formNode.target = frameId; - - if(this._formNode.encoding){ - // IE does not respect property enctype for HTML forms. - // Instead use property encoding. - this._formNode.encoding = 'multipart/form-data'; - } - else{ - this._formNode.enctype = 'multipart/form-data'; - } - - if(postData){ - var oElements = this.appendPostData(postData); - } - - this._formNode.submit(); - - if(oElements && oElements.length > 0){ - try - { - for(var i=0; i < oElements.length; i++){ - this._formNode.removeChild(oElements[i]); - } - } - catch(e){} - } - - // Reset HTML form status properties. - this.resetFormState(); - - // Create the upload callback handler that fires when the iframe - // receives the load event. Subsequently, the event handler is detached - // and the iframe removed from the document. - - var uploadCallback = function() - { - var obj = {}; - obj.tId = id; - obj.argument = callback.argument; - - try - { - obj.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null; - obj.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document; - } - catch(e){} - - if(callback.upload){ - if(!callback.scope){ - callback.upload(obj); - } - else{ - callback.upload.apply(callback.scope, [obj]); - } - } - - if(YAHOO.util.Event){ - YAHOO.util.Event.removeListener(io, "load", uploadCallback); - } - else if(window.detachEvent){ - io.detachEvent('onload', uploadCallback); - } - else{ - io.removeEventListener('load', uploadCallback, false); - } - setTimeout(function(){ document.body.removeChild(io); }, 100); - }; - - - // Bind the onload handler to the iframe to detect the file upload response. - if(YAHOO.util.Event){ - YAHOO.util.Event.addListener(io, "load", uploadCallback); - } - else if(window.attachEvent){ - io.attachEvent('onload', uploadCallback); - } - else{ - io.addEventListener('load', uploadCallback, false); - } - }, - - /** - * @description Method to terminate a transaction, if it has not reached readyState 4. - * @method abort - * @public - * @static - * @param {object} o The connection object returned by asyncRequest. - * @param {object} callback User-defined callback object. - * @param {string} isTimeout boolean to indicate if abort was a timeout. - * @return {boolean} - */ - abort:function(o, callback, isTimeout) - { - if(this.isCallInProgress(o)){ - o.conn.abort(); - window.clearInterval(this._poll[o.tId]); - delete this._poll[o.tId]; - if(isTimeout){ - delete this._timeOut[o.tId]; - } - - this.handleTransactionResponse(o, callback, true); - - return true; - } - else{ - return false; - } - }, - - /** - * Public method to check if the transaction is still being processed. - * - * @method isCallInProgress - * @public - * @static - * @param {object} o The connection object returned by asyncRequest - * @return {boolean} - */ - isCallInProgress:function(o) - { - // if the XHR object assigned to the transaction has not been dereferenced, - // then check its readyState status. Otherwise, return false. - if(o.conn){ - return o.conn.readyState != 4 && o.conn.readyState != 0; - } - else{ - //The XHR object has been destroyed. - return false; - } - }, - - /** - * @description Dereference the XHR instance and the connection object after the transaction is completed. - * @method releaseObject - * @private - * @static - * @param {object} o The connection object - * @return {void} - */ - releaseObject:function(o) - { - //dereference the XHR instance. - o.conn = null; - //dereference the connection object. - o = null; - } +/* +Copyright (c) 2006, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 0.12.2 +*/ +/** + * The Connection Manager provides a simplified interface to the XMLHttpRequest + * object. It handles cross-browser instantiantion of XMLHttpRequest, negotiates the + * interactive states and server response, returning the results to a pre-defined + * callback you create. + * + * @namespace YAHOO.util + * @module connection + * @requires yahoo + */ + +/** + * The Connection Manager singleton provides methods for creating and managing + * asynchronous transactions. + * + * @class Connect + */ +YAHOO.util.Connect = +{ + /** + * @description Array of MSFT ActiveX ids for XMLHttpRequest. + * @property _msxml_progid + * @private + * @static + * @type array + */ + _msxml_progid:[ + 'MSXML2.XMLHTTP.3.0', + 'MSXML2.XMLHTTP', + 'Microsoft.XMLHTTP' + ], + + /** + * @description Object literal of HTTP header(s) + * @property _http_header + * @private + * @static + * @type object + */ + _http_header:{}, + + /** + * @description Determines if HTTP headers are set. + * @property _has_http_headers + * @private + * @static + * @type boolean + */ + _has_http_headers:false, + + /** + * @description Determines if a default header of + * Content-Type of 'application/x-www-form-urlencoded' + * will be added to any client HTTP headers sent for POST + * transactions. + * @property _use_default_post_header + * @private + * @static + * @type boolean + */ + _use_default_post_header:true, + + /** + * @description Determines if a default header of + * Content-Type of 'application/x-www-form-urlencoded' + * will be added to any client HTTP headers sent for POST + * transactions. + * @property _default_post_header + * @private + * @static + * @type boolean + */ + _default_post_header:'application/x-www-form-urlencoded', + + /** + * @description Property modified by setForm() to determine if the data + * should be submitted as an HTML form. + * @property _isFormSubmit + * @private + * @static + * @type boolean + */ + _isFormSubmit:false, + + /** + * @description Property modified by setForm() to determine if a file(s) + * upload is expected. + * @property _isFileUpload + * @private + * @static + * @type boolean + */ + _isFileUpload:false, + + /** + * @description Property modified by setForm() to set a reference to the HTML + * form node if the desired action is file upload. + * @property _formNode + * @private + * @static + * @type object + */ + _formNode:null, + + /** + * @description Property modified by setForm() to set the HTML form data + * for each transaction. + * @property _sFormData + * @private + * @static + * @type string + */ + _sFormData:null, + + /** + * @description Collection of polling references to the polling mechanism in handleReadyState. + * @property _poll + * @private + * @static + * @type object + */ + _poll:{}, + + /** + * @description Queue of timeout values for each transaction callback with a defined timeout value. + * @property _timeOut + * @private + * @static + * @type object + */ + _timeOut:{}, + + /** + * @description The polling frequency, in milliseconds, for HandleReadyState. + * when attempting to determine a transaction's XHR readyState. + * The default is 50 milliseconds. + * @property _polling_interval + * @private + * @static + * @type int + */ + _polling_interval:50, + + /** + * @description A transaction counter that increments the transaction id for each transaction. + * @property _transaction_id + * @private + * @static + * @type int + */ + _transaction_id:0, + + /** + * @description Member to add an ActiveX id to the existing xml_progid array. + * In the event(unlikely) a new ActiveX id is introduced, it can be added + * without internal code modifications. + * @method setProgId + * @public + * @static + * @param {string} id The ActiveX id to be added to initialize the XHR object. + * @return void + */ + setProgId:function(id) + { + this._msxml_progid.unshift(id); + }, + + /** + * @description Member to enable or disable the default POST header. + * @method setDefaultPostHeader + * @public + * @static + * @param {boolean} b Set and use default header - true or false . + * @return void + */ + setDefaultPostHeader:function(b) + { + this._use_default_post_header = b; + }, + + /** + * @description Member to modify the default polling interval. + * @method setPollingInterval + * @public + * @static + * @param {int} i The polling interval in milliseconds. + * @return void + */ + setPollingInterval:function(i) + { + if(typeof i == 'number' && isFinite(i)){ + this._polling_interval = i; + } + }, + + /** + * @description Instantiates a XMLHttpRequest object and returns an object with two properties: + * the XMLHttpRequest instance and the transaction id. + * @method createXhrObject + * @private + * @static + * @param {int} transactionId Property containing the transaction id for this transaction. + * @return object + */ + createXhrObject:function(transactionId) + { + var obj,http; + try + { + // Instantiates XMLHttpRequest in non-IE browsers and assigns to http. + http = new XMLHttpRequest(); + // Object literal with http and tId properties + obj = { conn:http, tId:transactionId }; + } + catch(e) + { + for(var i=0; i= 200 && httpStatus < 300){ + try + { + responseObject = this.createResponseObject(o, callback.argument); + if(callback.success){ + if(!callback.scope){ + callback.success(responseObject); + } + else{ + // If a scope property is defined, the callback will be fired from + // the context of the object. + callback.success.apply(callback.scope, [responseObject]); + } + } + } + catch(e){} + } + else{ + try + { + switch(httpStatus){ + // The following cases are wininet.dll error codes that may be encountered. + case 12002: // Server timeout + case 12029: // 12029 to 12031 correspond to dropped connections. + case 12030: + case 12031: + case 12152: // Connection closed by server. + case 13030: // See above comments for variable status. + responseObject = this.createExceptionObject(o.tId, callback.argument, (isAbort?isAbort:false)); + if(callback.failure){ + if(!callback.scope){ + callback.failure(responseObject); + } + else{ + callback.failure.apply(callback.scope, [responseObject]); + } + } + break; + default: + responseObject = this.createResponseObject(o, callback.argument); + if(callback.failure){ + if(!callback.scope){ + callback.failure(responseObject); + } + else{ + callback.failure.apply(callback.scope, [responseObject]); + } + } + } + } + catch(e){} + } + + this.releaseObject(o); + responseObject = null; + }, + + /** + * @description This method evaluates the server response, creates and returns the results via + * its properties. Success and failure cases will differ in the response + * object's property values. + * @method createResponseObject + * @private + * @static + * @param {object} o The connection object + * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback + * @return {object} + */ + createResponseObject:function(o, callbackArg) + { + var obj = {}; + var headerObj = {}; + + try + { + var headerStr = o.conn.getAllResponseHeaders(); + var header = headerStr.split('\n'); + for(var i=0; i'); + + // IE will throw a security exception in an SSL environment if the + // iframe source is undefined. + if(typeof secureUri == 'boolean'){ + io.src = 'javascript:false'; + } + else if(typeof secureURI == 'string'){ + // Deprecated + io.src = secureUri; + } + } + else{ + var io = document.createElement('iframe'); + io.id = frameId; + io.name = frameId; + } + + io.style.position = 'absolute'; + io.style.top = '-1000px'; + io.style.left = '-1000px'; + + document.body.appendChild(io); + }, + + /** + * @description Parses the POST data and creates hidden form elements + * for each key-value, and appends them to the HTML form object. + * @method appendPostData + * @private + * @static + * @param {string} postData The HTTP POST data + * @return {array} formElements Collection of hidden fields. + */ + appendPostData:function(postData) + { + var formElements = []; + var postMessage = postData.split('&'); + for(var i=0; i < postMessage.length; i++){ + var delimitPos = postMessage[i].indexOf('='); + if(delimitPos != -1){ + formElements[i] = document.createElement('input'); + formElements[i].type = 'hidden'; + formElements[i].name = postMessage[i].substring(0,delimitPos); + formElements[i].value = postMessage[i].substring(delimitPos+1); + this._formNode.appendChild(formElements[i]); + } + } + + return formElements; + }, + + /** + * @description Uploads HTML form, including files/attachments, to the + * iframe created in createFrame. + * @method uploadFile + * @private + * @static + * @param {int} id The transaction id. + * @param {object} callback - User-defined callback object. + * @param {string} uri Fully qualified path of resource. + * @return {void} + */ + uploadFile:function(id, callback, uri, postData){ + + // Each iframe has an id prefix of "yuiIO" followed + // by the unique transaction id. + var frameId = 'yuiIO' + id; + var io = document.getElementById(frameId); + + // Initialize the HTML form properties in case they are + // not defined in the HTML form. + this._formNode.action = uri; + this._formNode.method = 'POST'; + this._formNode.target = frameId; + + if(this._formNode.encoding){ + // IE does not respect property enctype for HTML forms. + // Instead use property encoding. + this._formNode.encoding = 'multipart/form-data'; + } + else{ + this._formNode.enctype = 'multipart/form-data'; + } + + if(postData){ + var oElements = this.appendPostData(postData); + } + + this._formNode.submit(); + + if(oElements && oElements.length > 0){ + try + { + for(var i=0; i < oElements.length; i++){ + this._formNode.removeChild(oElements[i]); + } + } + catch(e){} + } + + // Reset HTML form status properties. + this.resetFormState(); + + // Create the upload callback handler that fires when the iframe + // receives the load event. Subsequently, the event handler is detached + // and the iframe removed from the document. + + var uploadCallback = function() + { + var obj = {}; + obj.tId = id; + obj.argument = callback.argument; + + try + { + obj.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null; + obj.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document; + } + catch(e){} + + if(callback.upload){ + if(!callback.scope){ + callback.upload(obj); + } + else{ + callback.upload.apply(callback.scope, [obj]); + } + } + + if(YAHOO.util.Event){ + YAHOO.util.Event.removeListener(io, "load", uploadCallback); + } + else if(window.detachEvent){ + io.detachEvent('onload', uploadCallback); + } + else{ + io.removeEventListener('load', uploadCallback, false); + } + setTimeout(function(){ document.body.removeChild(io); }, 100); + }; + + + // Bind the onload handler to the iframe to detect the file upload response. + if(YAHOO.util.Event){ + YAHOO.util.Event.addListener(io, "load", uploadCallback); + } + else if(window.attachEvent){ + io.attachEvent('onload', uploadCallback); + } + else{ + io.addEventListener('load', uploadCallback, false); + } + }, + + /** + * @description Method to terminate a transaction, if it has not reached readyState 4. + * @method abort + * @public + * @static + * @param {object} o The connection object returned by asyncRequest. + * @param {object} callback User-defined callback object. + * @param {string} isTimeout boolean to indicate if abort was a timeout. + * @return {boolean} + */ + abort:function(o, callback, isTimeout) + { + if(this.isCallInProgress(o)){ + o.conn.abort(); + window.clearInterval(this._poll[o.tId]); + delete this._poll[o.tId]; + if(isTimeout){ + delete this._timeOut[o.tId]; + } + + this.handleTransactionResponse(o, callback, true); + + return true; + } + else{ + return false; + } + }, + + /** + * Public method to check if the transaction is still being processed. + * + * @method isCallInProgress + * @public + * @static + * @param {object} o The connection object returned by asyncRequest + * @return {boolean} + */ + isCallInProgress:function(o) + { + // if the XHR object assigned to the transaction has not been dereferenced, + // then check its readyState status. Otherwise, return false. + if(o.conn){ + return o.conn.readyState != 4 && o.conn.readyState != 0; + } + else{ + //The XHR object has been destroyed. + return false; + } + }, + + /** + * @description Dereference the XHR instance and the connection object after the transaction is completed. + * @method releaseObject + * @private + * @static + * @param {object} o The connection object + * @return {void} + */ + releaseObject:function(o) + { + //dereference the XHR instance. + o.conn = null; + //dereference the connection object. + o = null; + } }; \ No newline at end of file diff --git a/source/web/scripts/ajax/yahoo/dom/dom-min.js b/source/web/scripts/ajax/yahoo/dom/dom-min.js index 0f9d6ee4cf..36e62f93a3 100644 --- a/source/web/scripts/ajax/yahoo/dom/dom-min.js +++ b/source/web/scripts/ajax/yahoo/dom/dom-min.js @@ -1 +1,59 @@ -/* Copyright (c) 2006, Yahoo! Inc. All rights reserved.Code licensed under the BSD License:http://developer.yahoo.net/yui/license.txt version: 0.12.0 */(function(){var Y=YAHOO.util,getStyle,setStyle,id_counter=0,propertyCache={};var ua=navigator.userAgent.toLowerCase(),isOpera=(ua.indexOf('opera')>-1),isSafari=(ua.indexOf('safari')>-1),isGecko=(!isOpera&&!isSafari&&ua.indexOf('gecko')>-1),isIE=(!isOpera&&ua.indexOf('msie')>-1);var patterns={HYPHEN:/(-[a-z])/i};var toCamel=function(property){if(!patterns.HYPHEN.test(property)){return property;}if(propertyCache[property]){return propertyCache[property];}while(patterns.HYPHEN.exec(property)){property=property.replace(RegExp.$1,RegExp.$1.substr(1).toUpperCase());}propertyCache[property]=property;return property;};if(document.defaultView&&document.defaultView.getComputedStyle){getStyle=function(el,property){var value=null;var computed=document.defaultView.getComputedStyle(el,'');if(computed){value=computed[toCamel(property)];}return el.style[property]||value;};}else if(document.documentElement.currentStyle&&isIE){getStyle=function(el,property){switch(toCamel(property)){case'opacity':var val=100;try{val=el.filters['DXImageTransform.Microsoft.Alpha'].opacity;}catch(e){try{val=el.filters('alpha').opacity;}catch(e){}}return val/100;break;default:var value=el.currentStyle?el.currentStyle[property]:null;return(el.style[property]||value);}};}else{getStyle=function(el,property){return el.style[property];};}if(isIE){setStyle=function(el,property,val){switch(property){case'opacity':if(typeof el.style.filter=='string'){el.style.filter='alpha(opacity='+val*100+')';if(!el.currentStyle||!el.currentStyle.hasLayout){el.style.zoom=1;}}break;default:el.style[property]=val;}};}else{setStyle=function(el,property,val){el.style[property]=val;};}YAHOO.util.Dom={get:function(el){if(!el){return null;}if(typeof el!='string'&&!(el instanceof Array)){return el;}if(typeof el=='string'){return document.getElementById(el);}else{var collection=[];for(var i=0,len=el.length;i=this.left&®ion.right<=this.right&®ion.top>=this.top&®ion.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(region){var t=Math.max(this.top,region.top);var r=Math.min(this.right,region.right);var b=Math.min(this.bottom,region.bottom);var l=Math.max(this.left,region.left);if(b>=t&&r>=l){return new YAHOO.util.Region(t,r,b,l);}else{return null;}};YAHOO.util.Region.prototype.union=function(region){var t=Math.min(this.top,region.top);var r=Math.max(this.right,region.right);var b=Math.max(this.bottom,region.bottom);var l=Math.min(this.left,region.left);return new YAHOO.util.Region(t,r,b,l);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+"}");};YAHOO.util.Region.getRegion=function(el){var p=YAHOO.util.Dom.getXY(el);var t=p[1];var r=p[0]+el.offsetWidth;var b=p[1]+el.offsetHeight;var l=p[0];return new YAHOO.util.Region(t,r,b,l);};YAHOO.util.Point=function(x,y){if(x instanceof Array){y=x[1];x=x[0];}this.x=this.right=this.left=this[0]=x;this.y=this.top=this.bottom=this[1]=y;};YAHOO.util.Point.prototype=new YAHOO.util.Region(); \ No newline at end of file +/* +Copyright (c) 2006, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 0.12.2 +*/ + +(function(){var Y=YAHOO.util,getStyle,setStyle,id_counter=0,propertyCache={};var ua=navigator.userAgent.toLowerCase(),isOpera=(ua.indexOf('opera')>-1),isSafari=(ua.indexOf('safari')>-1),isGecko=(!isOpera&&!isSafari&&ua.indexOf('gecko')>-1),isIE=(!isOpera&&ua.indexOf('msie')>-1);var patterns={HYPHEN:/(-[a-z])/i};var toCamel=function(property){if(!patterns.HYPHEN.test(property)){return property;} +if(propertyCache[property]){return propertyCache[property];} +while(patterns.HYPHEN.exec(property)){property=property.replace(RegExp.$1,RegExp.$1.substr(1).toUpperCase());} +propertyCache[property]=property;return property;};if(document.defaultView&&document.defaultView.getComputedStyle){getStyle=function(el,property){var value=null;var computed=document.defaultView.getComputedStyle(el,'');if(computed){value=computed[toCamel(property)];} +return el.style[property]||value;};}else if(document.documentElement.currentStyle&&isIE){getStyle=function(el,property){switch(toCamel(property)){case'opacity':var val=100;try{val=el.filters['DXImageTransform.Microsoft.Alpha'].opacity;}catch(e){try{val=el.filters('alpha').opacity;}catch(e){}} +return val/100;break;default:var value=el.currentStyle?el.currentStyle[property]:null;return(el.style[property]||value);}};}else{getStyle=function(el,property){return el.style[property];};} +if(isIE){setStyle=function(el,property,val){switch(property){case'opacity':if(typeof el.style.filter=='string'){el.style.filter='alpha(opacity='+val*100+')';if(!el.currentStyle||!el.currentStyle.hasLayout){el.style.zoom=1;}} +break;default:el.style[property]=val;}};}else{setStyle=function(el,property,val){el.style[property]=val;};} +YAHOO.util.Dom={get:function(el){if(!el){return null;} +if(typeof el!='string'&&!(el instanceof Array)){return el;} +if(typeof el=='string'){return document.getElementById(el);} +else{var collection=[];for(var i=0,len=el.length;i=this.left&®ion.right<=this.right&®ion.top>=this.top&®ion.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(region){var t=Math.max(this.top,region.top);var r=Math.min(this.right,region.right);var b=Math.min(this.bottom,region.bottom);var l=Math.max(this.left,region.left);if(b>=t&&r>=l){return new YAHOO.util.Region(t,r,b,l);}else{return null;}};YAHOO.util.Region.prototype.union=function(region){var t=Math.min(this.top,region.top);var r=Math.max(this.right,region.right);var b=Math.max(this.bottom,region.bottom);var l=Math.min(this.left,region.left);return new YAHOO.util.Region(t,r,b,l);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+"}");};YAHOO.util.Region.getRegion=function(el){var p=YAHOO.util.Dom.getXY(el);var t=p[1];var r=p[0]+el.offsetWidth;var b=p[1]+el.offsetHeight;var l=p[0];return new YAHOO.util.Region(t,r,b,l);};YAHOO.util.Point=function(x,y){if(x instanceof Array){y=x[1];x=x[0];} +this.x=this.right=this.left=this[0]=x;this.y=this.top=this.bottom=this[1]=y;};YAHOO.util.Point.prototype=new YAHOO.util.Region(); \ No newline at end of file diff --git a/source/web/scripts/ajax/yahoo/dom/dom.js b/source/web/scripts/ajax/yahoo/dom/dom.js index 6f04c43166..c37bd1b04e 100644 --- a/source/web/scripts/ajax/yahoo/dom/dom.js +++ b/source/web/scripts/ajax/yahoo/dom/dom.js @@ -1,881 +1,892 @@ -/* -Copyright (c) 2006, Yahoo! Inc. All rights reserved. -Code licensed under the BSD License: -http://developer.yahoo.net/yui/license.txt -version: 0.12.0 -*/ - -/** - * The dom module provides helper methods for manipulating Dom elements. - * @module dom - * - */ - -(function() { - var Y = YAHOO.util, // internal shorthand - getStyle, // for load time browser branching - setStyle, // ditto - id_counter = 0, // for use with generateId - propertyCache = {}; // for faster hyphen converts - - // brower detection - var ua = navigator.userAgent.toLowerCase(), - isOpera = (ua.indexOf('opera') > -1), - isSafari = (ua.indexOf('safari') > -1), - isGecko = (!isOpera && !isSafari && ua.indexOf('gecko') > -1), - isIE = (!isOpera && ua.indexOf('msie') > -1); - - // regex cache - var patterns = { - HYPHEN: /(-[a-z])/i - }; - - - var toCamel = function(property) { - if ( !patterns.HYPHEN.test(property) ) { - return property; // no hyphens - } - - if (propertyCache[property]) { // already converted - return propertyCache[property]; - } - - while( patterns.HYPHEN.exec(property) ) { - property = property.replace(RegExp.$1, - RegExp.$1.substr(1).toUpperCase()); - } - - propertyCache[property] = property; - return property; - //return property.replace(/-([a-z])/gi, function(m0, m1) {return m1.toUpperCase()}) // cant use function as 2nd arg yet due to safari bug - }; - - // branching at load instead of runtime - if (document.defaultView && document.defaultView.getComputedStyle) { // W3C DOM method - getStyle = function(el, property) { - var value = null; - - var computed = document.defaultView.getComputedStyle(el, ''); - if (computed) { // test computed before touching for safari - value = computed[toCamel(property)]; - } - - return el.style[property] || value; - }; - } else if (document.documentElement.currentStyle && isIE) { // IE method - getStyle = function(el, property) { - switch( toCamel(property) ) { - case 'opacity' :// IE opacity uses filter - var val = 100; - try { // will error if no DXImageTransform - val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity; - - } catch(e) { - try { // make sure its in the document - val = el.filters('alpha').opacity; - } catch(e) { - } - } - return val / 100; - break; - default: - // test currentStyle before touching - var value = el.currentStyle ? el.currentStyle[property] : null; - return ( el.style[property] || value ); - } - }; - } else { // default to inline only - getStyle = function(el, property) { return el.style[property]; }; - } - - if (isIE) { - setStyle = function(el, property, val) { - switch (property) { - case 'opacity': - if ( typeof el.style.filter == 'string' ) { // in case not appended - el.style.filter = 'alpha(opacity=' + val * 100 + ')'; - - if (!el.currentStyle || !el.currentStyle.hasLayout) { - el.style.zoom = 1; // when no layout or cant tell - } - } - break; - default: - el.style[property] = val; - } - }; - } else { - setStyle = function(el, property, val) { - el.style[property] = val; - }; - } - - /** - * Provides helper methods for DOM elements. - * @namespace YAHOO.util - * @class Dom - */ - YAHOO.util.Dom = { - /** - * Returns an HTMLElement reference. - * @method get - * @param {String | HTMLElement |Array} el Accepts a string to use as an ID for getting a DOM reference, an actual DOM reference, or an Array of IDs and/or HTMLElements. - * @return {HTMLElement | Array} A DOM reference to an HTML element or an array of HTMLElements. - */ - get: function(el) { - if (!el) { return null; } // nothing to work with - - if (typeof el != 'string' && !(el instanceof Array) ) { // assuming HTMLElement or HTMLCollection, so pass back as is - return el; - } - - if (typeof el == 'string') { // ID - return document.getElementById(el); - } - else { // array of ID's and/or elements - var collection = []; - for (var i = 0, len = el.length; i < len; ++i) { - collection[collection.length] = Y.Dom.get(el[i]); - } - - return collection; - } - - return null; // safety, should never happen - }, - - /** - * Normalizes currentStyle and ComputedStyle. - * @method getStyle - * @param {String | HTMLElement |Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. - * @param {String} property The style property whose value is returned. - * @return {String | Array} The current value of the style property for the element(s). - */ - getStyle: function(el, property) { - property = toCamel(property); - - var f = function(element) { - return getStyle(element, property); - }; - - return Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Wrapper for setting style properties of HTMLElements. Normalizes "opacity" across modern browsers. - * @method setStyle - * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. - * @param {String} property The style property to be set. - * @param {String} val The value to apply to the given property. - */ - setStyle: function(el, property, val) { - property = toCamel(property); - - var f = function(element) { - setStyle(element, property, val); - - }; - - Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Gets the current position of an element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). - * @method getXY - * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements - * @return {Array} The XY position of the element(s) - */ - getXY: function(el) { - var f = function(el) { - - // has to be part of document to have pageXY - if (el.parentNode === null || el.offsetParent === null || - this.getStyle(el, 'display') == 'none') { - return false; - } - - var parentNode = null; - var pos = []; - var box; - - if (el.getBoundingClientRect) { // IE - box = el.getBoundingClientRect(); - var doc = document; - if ( !this.inDocument(el) && parent.document != document) {// might be in a frame, need to get its scroll - doc = parent.document; - - if ( !this.isAncestor(doc.documentElement, el) ) { - return false; - } - - } - - var scrollTop = Math.max(doc.documentElement.scrollTop, doc.body.scrollTop); - var scrollLeft = Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft); - - return [box.left + scrollLeft, box.top + scrollTop]; - } - else { // safari, opera, & gecko - pos = [el.offsetLeft, el.offsetTop]; - parentNode = el.offsetParent; - if (parentNode != el) { - while (parentNode) { - pos[0] += parentNode.offsetLeft; - pos[1] += parentNode.offsetTop; - parentNode = parentNode.offsetParent; - } - } - if (isSafari && this.getStyle(el, 'position') == 'absolute' ) { // safari doubles in some cases - pos[0] -= document.body.offsetLeft; - pos[1] -= document.body.offsetTop; - } - } - - if (el.parentNode) { parentNode = el.parentNode; } - else { parentNode = null; } - - while (parentNode && parentNode.tagName.toUpperCase() != 'BODY' && parentNode.tagName.toUpperCase() != 'HTML') - { // account for any scrolled ancestors - if (Y.Dom.getStyle(parentNode, 'display') != 'inline') { // work around opera inline scrollLeft/Top bug - pos[0] -= parentNode.scrollLeft; - pos[1] -= parentNode.scrollTop; - } - - if (parentNode.parentNode) { - parentNode = parentNode.parentNode; - } else { parentNode = null; } - } - - - return pos; - }; - - return Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Gets the current X position of an element based on page coordinates. The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). - * @method getX - * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements - * @return {String | Array} The X position of the element(s) - */ - getX: function(el) { - var f = function(el) { - return Y.Dom.getXY(el)[0]; - }; - - return Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Gets the current Y position of an element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). - * @method getY - * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements - * @return {String | Array} The Y position of the element(s) - */ - getY: function(el) { - var f = function(el) { - return Y.Dom.getXY(el)[1]; - }; - - return Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Set the position of an html element in page coordinates, regardless of how the element is positioned. - * The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). - * @method setXY - * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements - * @param {Array} pos Contains X & Y values for new position (coordinates are page-based) - * @param {Boolean} noRetry By default we try and set the position a second time if the first fails - */ - setXY: function(el, pos, noRetry) { - var f = function(el) { - var style_pos = this.getStyle(el, 'position'); - if (style_pos == 'static') { // default to relative - this.setStyle(el, 'position', 'relative'); - style_pos = 'relative'; - } - - var pageXY = this.getXY(el); - if (pageXY === false) { // has to be part of doc to have pageXY - return false; - } - - var delta = [ // assuming pixels; if not we will have to retry - parseInt( this.getStyle(el, 'left'), 10 ), - parseInt( this.getStyle(el, 'top'), 10 ) - ]; - - if ( isNaN(delta[0]) ) {// in case of 'auto' - delta[0] = (style_pos == 'relative') ? 0 : el.offsetLeft; - } - if ( isNaN(delta[1]) ) { // in case of 'auto' - delta[1] = (style_pos == 'relative') ? 0 : el.offsetTop; - } - - if (pos[0] !== null) { el.style.left = pos[0] - pageXY[0] + delta[0] + 'px'; } - if (pos[1] !== null) { el.style.top = pos[1] - pageXY[1] + delta[1] + 'px'; } - - var newXY = this.getXY(el); - - // if retry is true, try one more time if we miss - if (!noRetry && (newXY[0] != pos[0] || newXY[1] != pos[1]) ) { - this.setXY(el, pos, true); - } - - }; - - Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Set the X position of an html element in page coordinates, regardless of how the element is positioned. - * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). - * @method setX - * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. - * @param {Int} x The value to use as the X coordinate for the element(s). - */ - setX: function(el, x) { - Y.Dom.setXY(el, [x, null]); - }, - - /** - * Set the Y position of an html element in page coordinates, regardless of how the element is positioned. - * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). - * @method setY - * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. - * @param {Int} x To use as the Y coordinate for the element(s). - */ - setY: function(el, y) { - Y.Dom.setXY(el, [null, y]); - }, - - /** - * Returns the region position of the given element. - * The element must be part of the DOM tree to have a region (display:none or elements not appended return false). - * @method getRegion - * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. - * @return {Region | Array} A Region or array of Region instances containing "top, left, bottom, right" member data. - */ - getRegion: function(el) { - var f = function(el) { - var region = new Y.Region.getRegion(el); - return region; - }; - - return Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Returns the width of the client (viewport). - * @method getClientWidth - * @deprecated Now using getViewportWidth. This interface left intact for back compat. - * @return {Int} The width of the viewable area of the page. - */ - getClientWidth: function() { - return Y.Dom.getViewportWidth(); - }, - - /** - * Returns the height of the client (viewport). - * @method getClientHeight - * @deprecated Now using getViewportHeight. This interface left intact for back compat. - * @return {Int} The height of the viewable area of the page. - */ - getClientHeight: function() { - return Y.Dom.getViewportHeight(); - }, - - /** - * Returns a array of HTMLElements with the given class. - * For optimized performance, include a tag and/or root node when possible. - * @method getElementsByClassName - * @param {String} className The class name to match against - * @param {String} tag (optional) The tag name of the elements being collected - * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point - * @return {Array} An array of elements that have the given class name - */ - getElementsByClassName: function(className, tag, root) { - var method = function(el) { return Y.Dom.hasClass(el, className); }; - return Y.Dom.getElementsBy(method, tag, root); - }, - - /** - * Determines whether an HTMLElement has the given className. - * @method hasClass - * @param {String | HTMLElement | Array} el The element or collection to test - * @param {String} className the class name to search for - * @return {Boolean | Array} A boolean value or array of boolean values - */ - hasClass: function(el, className) { - var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)'); - - var f = function(el) { - return re.test(el['className']); - }; - - return Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Adds a class name to a given element or collection of elements. - * @method addClass - * @param {String | HTMLElement | Array} el The element or collection to add the class to - * @param {String} className the class name to add to the class attribute - */ - addClass: function(el, className) { - var f = function(el) { - if (this.hasClass(el, className)) { return; } // already present - - - el['className'] = [el['className'], className].join(' '); - }; - - Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Removes a class name from a given element or collection of elements. - * @method removeClass - * @param {String | HTMLElement | Array} el The element or collection to remove the class from - * @param {String} className the class name to remove from the class attribute - */ - removeClass: function(el, className) { - var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)', 'g'); - - var f = function(el) { - if (!this.hasClass(el, className)) { return; } // not present - - - var c = el['className']; - el['className'] = c.replace(re, ' '); - if ( this.hasClass(el, className) ) { // in case of multiple adjacent - this.removeClass(el, className); - } - - }; - - Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Replace a class with another class for a given element or collection of elements. - * If no oldClassName is present, the newClassName is simply added. - * @method replaceClass - * @param {String | HTMLElement | Array} el The element or collection to remove the class from - * @param {String} oldClassName the class name to be replaced - * @param {String} newClassName the class name that will be replacing the old class name - */ - replaceClass: function(el, oldClassName, newClassName) { - if (oldClassName === newClassName) { // avoid infinite loop - return false; - } - - var re = new RegExp('(?:^|\\s+)' + oldClassName + '(?:\\s+|$)', 'g'); - - var f = function(el) { - - if ( !this.hasClass(el, oldClassName) ) { - this.addClass(el, newClassName); // just add it if nothing to replace - return; // note return - } - - el['className'] = el['className'].replace(re, ' ' + newClassName + ' '); - - if ( this.hasClass(el, oldClassName) ) { // in case of multiple adjacent - this.replaceClass(el, oldClassName, newClassName); - } - }; - - Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Generates a unique ID - * @method generateId - * @param {String | HTMLElement | Array} el (optional) An optional element array of elements to add an ID to (no ID is added if one is already present). - * @param {String} prefix (optional) an optional prefix to use (defaults to "yui-gen"). - * @return {String | Array} The generated ID, or array of generated IDs (or original ID if already present on an element) - */ - generateId: function(el, prefix) { - prefix = prefix || 'yui-gen'; - el = el || {}; - - var f = function(el) { - if (el) { - el = Y.Dom.get(el); - } else { - el = {}; // just generating ID in this case - } - - if (!el.id) { - el.id = prefix + id_counter++; - } // dont override existing - - - return el.id; - }; - - return Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Determines whether an HTMLElement is an ancestor of another HTML element in the DOM hierarchy. - * @method isAncestor - * @param {String | HTMLElement} haystack The possible ancestor - * @param {String | HTMLElement} needle The possible descendent - * @return {Boolean} Whether or not the haystack is an ancestor of needle - */ - isAncestor: function(haystack, needle) { - haystack = Y.Dom.get(haystack); - if (!haystack || !needle) { return false; } - - var f = function(needle) { - if (haystack.contains && !isSafari) { // safari "contains" is broken - return haystack.contains(needle); - } - else if ( haystack.compareDocumentPosition ) { - return !!(haystack.compareDocumentPosition(needle) & 16); - } - else { // loop up and test each parent - var parent = needle.parentNode; - - while (parent) { - if (parent == haystack) { - return true; - } - else if (!parent.tagName || parent.tagName.toUpperCase() == 'HTML') { - return false; - } - - parent = parent.parentNode; - } - return false; - } - }; - - return Y.Dom.batch(needle, f, Y.Dom, true); - }, - - /** - * Determines whether an HTMLElement is present in the current document. - * @method inDocument - * @param {String | HTMLElement} el The element to search for - * @return {Boolean} Whether or not the element is present in the current document - */ - inDocument: function(el) { - var f = function(el) { - return this.isAncestor(document.documentElement, el); - }; - - return Y.Dom.batch(el, f, Y.Dom, true); - }, - - /** - * Returns a array of HTMLElements that pass the test applied by supplied boolean method. - * For optimized performance, include a tag and/or root node when possible. - * @method getElementsBy - * @param {Function} method - A boolean method for testing elements which receives the element as its only argument. - - * @param {String} tag (optional) The tag name of the elements being collected - * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point - */ - getElementsBy: function(method, tag, root) { - tag = tag || '*'; - root = Y.Dom.get(root) || document; - - var nodes = []; - var elements = root.getElementsByTagName(tag); - - if ( !elements.length && (tag == '*' && root.all) ) { - elements = root.all; // IE < 6 - } - - for (var i = 0, len = elements.length; i < len; ++i) { - if ( method(elements[i]) ) { nodes[nodes.length] = elements[i]; } - } - - - return nodes; - }, - - /** - * Returns an array of elements that have had the supplied method applied. - * The method is called with the element(s) as the first arg, and the optional param as the second ( method(el, o) ). - * @method batch - * @param {String | HTMLElement | Array} el (optional) An element or array of elements to apply the method to - * @param {Function} method The method to apply to the element(s) - * @param {Any} o (optional) An optional arg that is passed to the supplied method - * @param {Boolean} override (optional) Whether or not to override the scope of "method" with "o" - * @return {HTMLElement | Array} The element(s) with the method applied - */ - batch: function(el, method, o, override) { - var id = el; - el = Y.Dom.get(el); - - var scope = (override) ? o : window; - - if (!el || el.tagName || !el.length) { // is null or not a collection (tagName for SELECT and others that can be both an element and a collection) - if (!el) { - return false; - } - return method.call(scope, el, o); - } - - var collection = []; - - for (var i = 0, len = el.length; i < len; ++i) { - if (!el[i]) { - id = el[i]; - } - collection[collection.length] = method.call(scope, el[i], o); - } - - return collection; - }, - - /** - * Returns the height of the document. - * @method getDocumentHeight - * @return {Int} The height of the actual document (which includes the body and its margin). - */ - getDocumentHeight: function() { - var scrollHeight = (document.compatMode != 'CSS1Compat') ? document.body.scrollHeight : document.documentElement.scrollHeight; - - var h = Math.max(scrollHeight, Y.Dom.getViewportHeight()); - return h; - }, - - /** - * Returns the width of the document. - * @method getDocumentWidth - * @return {Int} The width of the actual document (which includes the body and its margin). - */ - getDocumentWidth: function() { - var scrollWidth = (document.compatMode != 'CSS1Compat') ? document.body.scrollWidth : document.documentElement.scrollWidth; - var w = Math.max(scrollWidth, Y.Dom.getViewportWidth()); - return w; - }, - - /** - * Returns the current height of the viewport. - * @method getViewportHeight - * @return {Int} The height of the viewable area of the page (excludes scrollbars). - */ - getViewportHeight: function() { - var height = self.innerHeight; // Safari, Opera - var mode = document.compatMode; - - if ( (mode || isIE) && !isOpera ) { // IE, Gecko - height = (mode == 'CSS1Compat') ? - document.documentElement.clientHeight : // Standards - document.body.clientHeight; // Quirks - } - - return height; - }, - - /** - * Returns the current width of the viewport. - * @method getViewportWidth - * @return {Int} The width of the viewable area of the page (excludes scrollbars). - */ - - getViewportWidth: function() { - var width = self.innerWidth; // Safari - var mode = document.compatMode; - - if (mode || isIE) { // IE, Gecko, Opera - width = (mode == 'CSS1Compat') ? - document.documentElement.clientWidth : // Standards - document.body.clientWidth; // Quirks - } - return width; - } - }; -})(); -/** - * A region is a representation of an object on a grid. It is defined - * by the top, right, bottom, left extents, so is rectangular by default. If - * other shapes are required, this class could be extended to support it. - * @namespace YAHOO.util - * @class Region - * @param {Int} t the top extent - * @param {Int} r the right extent - * @param {Int} b the bottom extent - * @param {Int} l the left extent - * @constructor - */ -YAHOO.util.Region = function(t, r, b, l) { - - /** - * The region's top extent - * @property top - * @type Int - */ - this.top = t; - - /** - * The region's top extent as index, for symmetry with set/getXY - * @property 1 - * @type Int - */ - this[1] = t; - - /** - * The region's right extent - * @property right - * @type int - */ - this.right = r; - - /** - * The region's bottom extent - * @property bottom - * @type Int - */ - this.bottom = b; - - /** - * The region's left extent - * @property left - * @type Int - */ - this.left = l; - - /** - * The region's left extent as index, for symmetry with set/getXY - * @property 0 - * @type Int - */ - this[0] = l; -}; - -/** - * Returns true if this region contains the region passed in - * @method contains - * @param {Region} region The region to evaluate - * @return {Boolean} True if the region is contained with this region, - * else false - */ -YAHOO.util.Region.prototype.contains = function(region) { - return ( region.left >= this.left && - region.right <= this.right && - region.top >= this.top && - region.bottom <= this.bottom ); - -}; - -/** - * Returns the area of the region - * @method getArea - * @return {Int} the region's area - */ -YAHOO.util.Region.prototype.getArea = function() { - return ( (this.bottom - this.top) * (this.right - this.left) ); -}; - -/** - * Returns the region where the passed in region overlaps with this one - * @method intersect - * @param {Region} region The region that intersects - * @return {Region} The overlap region, or null if there is no overlap - */ -YAHOO.util.Region.prototype.intersect = function(region) { - var t = Math.max( this.top, region.top ); - var r = Math.min( this.right, region.right ); - var b = Math.min( this.bottom, region.bottom ); - var l = Math.max( this.left, region.left ); - - if (b >= t && r >= l) { - return new YAHOO.util.Region(t, r, b, l); - } else { - return null; - } -}; - -/** - * Returns the region representing the smallest region that can contain both - * the passed in region and this region. - * @method union - * @param {Region} region The region that to create the union with - * @return {Region} The union region - */ -YAHOO.util.Region.prototype.union = function(region) { - var t = Math.min( this.top, region.top ); - var r = Math.max( this.right, region.right ); - var b = Math.max( this.bottom, region.bottom ); - var l = Math.min( this.left, region.left ); - - return new YAHOO.util.Region(t, r, b, l); -}; - -/** - * toString - * @method toString - * @return string the region properties - */ -YAHOO.util.Region.prototype.toString = function() { - return ( "Region {" + - "top: " + this.top + - ", right: " + this.right + - ", bottom: " + this.bottom + - ", left: " + this.left + - "}" ); -}; - -/** - * Returns a region that is occupied by the DOM element - * @method getRegion - * @param {HTMLElement} el The element - * @return {Region} The region that the element occupies - * @static - */ -YAHOO.util.Region.getRegion = function(el) { - var p = YAHOO.util.Dom.getXY(el); - - var t = p[1]; - var r = p[0] + el.offsetWidth; - var b = p[1] + el.offsetHeight; - var l = p[0]; - - return new YAHOO.util.Region(t, r, b, l); -}; - -///////////////////////////////////////////////////////////////////////////// - -/** - * A point is a region that is special in that it represents a single point on - * the grid. - * @namespace YAHOO.util - * @class Point - * @param {Int} x The X position of the point - * @param {Int} y The Y position of the point - * @constructor - * @extends YAHOO.util.Region - */ -YAHOO.util.Point = function(x, y) { - if (x instanceof Array) { // accept output from Dom.getXY - y = x[1]; - x = x[0]; - } - - /** - * The X position of the point, which is also the right, left and index zero (for Dom.getXY symmetry) - * @property x - * @type Int - */ - - this.x = this.right = this.left = this[0] = x; - - /** - * The Y position of the point, which is also the top, bottom and index one (for Dom.getXY symmetry) - * @property y - * @type Int - */ - this.y = this.top = this.bottom = this[1] = y; -}; - -YAHOO.util.Point.prototype = new YAHOO.util.Region(); - +/* +Copyright (c) 2006, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 0.12.2 +*/ +/** + * The dom module provides helper methods for manipulating Dom elements. + * @module dom + * + */ + +(function() { + var Y = YAHOO.util, // internal shorthand + getStyle, // for load time browser branching + setStyle, // ditto + id_counter = 0, // for use with generateId + propertyCache = {}; // for faster hyphen converts + + // brower detection + var ua = navigator.userAgent.toLowerCase(), + isOpera = (ua.indexOf('opera') > -1), + isSafari = (ua.indexOf('safari') > -1), + isGecko = (!isOpera && !isSafari && ua.indexOf('gecko') > -1), + isIE = (!isOpera && ua.indexOf('msie') > -1); + + // regex cache + var patterns = { + HYPHEN: /(-[a-z])/i + }; + + + var toCamel = function(property) { + if ( !patterns.HYPHEN.test(property) ) { + return property; // no hyphens + } + + if (propertyCache[property]) { // already converted + return propertyCache[property]; + } + + while( patterns.HYPHEN.exec(property) ) { + property = property.replace(RegExp.$1, + RegExp.$1.substr(1).toUpperCase()); + } + + propertyCache[property] = property; + return property; + //return property.replace(/-([a-z])/gi, function(m0, m1) {return m1.toUpperCase()}) // cant use function as 2nd arg yet due to safari bug + }; + + // branching at load instead of runtime + if (document.defaultView && document.defaultView.getComputedStyle) { // W3C DOM method + getStyle = function(el, property) { + var value = null; + + var computed = document.defaultView.getComputedStyle(el, ''); + if (computed) { // test computed before touching for safari + value = computed[toCamel(property)]; + } + + return el.style[property] || value; + }; + } else if (document.documentElement.currentStyle && isIE) { // IE method + getStyle = function(el, property) { + switch( toCamel(property) ) { + case 'opacity' :// IE opacity uses filter + var val = 100; + try { // will error if no DXImageTransform + val = el.filters['DXImageTransform.Microsoft.Alpha'].opacity; + + } catch(e) { + try { // make sure its in the document + val = el.filters('alpha').opacity; + } catch(e) { + } + } + return val / 100; + break; + default: + // test currentStyle before touching + var value = el.currentStyle ? el.currentStyle[property] : null; + return ( el.style[property] || value ); + } + }; + } else { // default to inline only + getStyle = function(el, property) { return el.style[property]; }; + } + + if (isIE) { + setStyle = function(el, property, val) { + switch (property) { + case 'opacity': + if ( typeof el.style.filter == 'string' ) { // in case not appended + el.style.filter = 'alpha(opacity=' + val * 100 + ')'; + + if (!el.currentStyle || !el.currentStyle.hasLayout) { + el.style.zoom = 1; // when no layout or cant tell + } + } + break; + default: + el.style[property] = val; + } + }; + } else { + setStyle = function(el, property, val) { + el.style[property] = val; + }; + } + + /** + * Provides helper methods for DOM elements. + * @namespace YAHOO.util + * @class Dom + */ + YAHOO.util.Dom = { + /** + * Returns an HTMLElement reference. + * @method get + * @param {String | HTMLElement |Array} el Accepts a string to use as an ID for getting a DOM reference, an actual DOM reference, or an Array of IDs and/or HTMLElements. + * @return {HTMLElement | Array} A DOM reference to an HTML element or an array of HTMLElements. + */ + get: function(el) { + if (!el) { return null; } // nothing to work with + + if (typeof el != 'string' && !(el instanceof Array) ) { // assuming HTMLElement or HTMLCollection, so pass back as is + return el; + } + + if (typeof el == 'string') { // ID + return document.getElementById(el); + } + else { // array of ID's and/or elements + var collection = []; + for (var i = 0, len = el.length; i < len; ++i) { + collection[collection.length] = Y.Dom.get(el[i]); + } + + return collection; + } + + return null; // safety, should never happen + }, + + /** + * Normalizes currentStyle and ComputedStyle. + * @method getStyle + * @param {String | HTMLElement |Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. + * @param {String} property The style property whose value is returned. + * @return {String | Array} The current value of the style property for the element(s). + */ + getStyle: function(el, property) { + property = toCamel(property); + + var f = function(element) { + return getStyle(element, property); + }; + + return Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Wrapper for setting style properties of HTMLElements. Normalizes "opacity" across modern browsers. + * @method setStyle + * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. + * @param {String} property The style property to be set. + * @param {String} val The value to apply to the given property. + */ + setStyle: function(el, property, val) { + property = toCamel(property); + + var f = function(element) { + setStyle(element, property, val); + + }; + + Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Gets the current position of an element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). + * @method getXY + * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements + * @return {Array} The XY position of the element(s) + */ + getXY: function(el) { + var f = function(el) { + + // has to be part of document to have pageXY + if (el.parentNode === null || el.offsetParent === null || + this.getStyle(el, 'display') == 'none') { + return false; + } + + var parentNode = null; + var pos = []; + var box; + + if (el.getBoundingClientRect) { // IE + box = el.getBoundingClientRect(); + var doc = document; + if ( !this.inDocument(el) && parent.document != document) {// might be in a frame, need to get its scroll + doc = parent.document; + + if ( !this.isAncestor(doc.documentElement, el) ) { + return false; + } + + } + + var scrollTop = Math.max(doc.documentElement.scrollTop, doc.body.scrollTop); + var scrollLeft = Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft); + + return [box.left + scrollLeft, box.top + scrollTop]; + } + else { // safari, opera, & gecko + pos = [el.offsetLeft, el.offsetTop]; + parentNode = el.offsetParent; + if (parentNode != el) { + while (parentNode) { + pos[0] += parentNode.offsetLeft; + pos[1] += parentNode.offsetTop; + parentNode = parentNode.offsetParent; + } + } + if (isSafari && this.getStyle(el, 'position') == 'absolute' ) { // safari doubles in some cases + pos[0] -= document.body.offsetLeft; + pos[1] -= document.body.offsetTop; + } + } + + if (el.parentNode) { parentNode = el.parentNode; } + else { parentNode = null; } + + while (parentNode && parentNode.tagName.toUpperCase() != 'BODY' && parentNode.tagName.toUpperCase() != 'HTML') + { // account for any scrolled ancestors + if (Y.Dom.getStyle(parentNode, 'display') != 'inline') { // work around opera inline scrollLeft/Top bug + pos[0] -= parentNode.scrollLeft; + pos[1] -= parentNode.scrollTop; + } + + if (parentNode.parentNode) { + parentNode = parentNode.parentNode; + } else { parentNode = null; } + } + + + return pos; + }; + + return Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Gets the current X position of an element based on page coordinates. The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). + * @method getX + * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements + * @return {String | Array} The X position of the element(s) + */ + getX: function(el) { + var f = function(el) { + return Y.Dom.getXY(el)[0]; + }; + + return Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Gets the current Y position of an element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). + * @method getY + * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements + * @return {String | Array} The Y position of the element(s) + */ + getY: function(el) { + var f = function(el) { + return Y.Dom.getXY(el)[1]; + }; + + return Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Set the position of an html element in page coordinates, regardless of how the element is positioned. + * The element(s) must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). + * @method setXY + * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements + * @param {Array} pos Contains X & Y values for new position (coordinates are page-based) + * @param {Boolean} noRetry By default we try and set the position a second time if the first fails + */ + setXY: function(el, pos, noRetry) { + var f = function(el) { + var style_pos = this.getStyle(el, 'position'); + if (style_pos == 'static') { // default to relative + this.setStyle(el, 'position', 'relative'); + style_pos = 'relative'; + } + + var pageXY = this.getXY(el); + if (pageXY === false) { // has to be part of doc to have pageXY + return false; + } + + var delta = [ // assuming pixels; if not we will have to retry + parseInt( this.getStyle(el, 'left'), 10 ), + parseInt( this.getStyle(el, 'top'), 10 ) + ]; + + if ( isNaN(delta[0]) ) {// in case of 'auto' + delta[0] = (style_pos == 'relative') ? 0 : el.offsetLeft; + } + if ( isNaN(delta[1]) ) { // in case of 'auto' + delta[1] = (style_pos == 'relative') ? 0 : el.offsetTop; + } + + if (pos[0] !== null) { el.style.left = pos[0] - pageXY[0] + delta[0] + 'px'; } + if (pos[1] !== null) { el.style.top = pos[1] - pageXY[1] + delta[1] + 'px'; } + + if (!noRetry) { + var newXY = this.getXY(el); + + // if retry is true, try one more time if we miss + if ( (pos[0] !== null && newXY[0] != pos[0]) || + (pos[1] !== null && newXY[1] != pos[1]) ) { + this.setXY(el, pos, true); + } + } + + }; + + Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Set the X position of an html element in page coordinates, regardless of how the element is positioned. + * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). + * @method setX + * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. + * @param {Int} x The value to use as the X coordinate for the element(s). + */ + setX: function(el, x) { + Y.Dom.setXY(el, [x, null]); + }, + + /** + * Set the Y position of an html element in page coordinates, regardless of how the element is positioned. + * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false). + * @method setY + * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. + * @param {Int} x To use as the Y coordinate for the element(s). + */ + setY: function(el, y) { + Y.Dom.setXY(el, [null, y]); + }, + + /** + * Returns the region position of the given element. + * The element must be part of the DOM tree to have a region (display:none or elements not appended return false). + * @method getRegion + * @param {String | HTMLElement | Array} el Accepts a string to use as an ID, an actual DOM reference, or an Array of IDs and/or HTMLElements. + * @return {Region | Array} A Region or array of Region instances containing "top, left, bottom, right" member data. + */ + getRegion: function(el) { + var f = function(el) { + var region = new Y.Region.getRegion(el); + return region; + }; + + return Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Returns the width of the client (viewport). + * @method getClientWidth + * @deprecated Now using getViewportWidth. This interface left intact for back compat. + * @return {Int} The width of the viewable area of the page. + */ + getClientWidth: function() { + return Y.Dom.getViewportWidth(); + }, + + /** + * Returns the height of the client (viewport). + * @method getClientHeight + * @deprecated Now using getViewportHeight. This interface left intact for back compat. + * @return {Int} The height of the viewable area of the page. + */ + getClientHeight: function() { + return Y.Dom.getViewportHeight(); + }, + + /** + * Returns a array of HTMLElements with the given class. + * For optimized performance, include a tag and/or root node when possible. + * @method getElementsByClassName + * @param {String} className The class name to match against + * @param {String} tag (optional) The tag name of the elements being collected + * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point + * @return {Array} An array of elements that have the given class name + */ + getElementsByClassName: function(className, tag, root) { + var method = function(el) { return Y.Dom.hasClass(el, className); }; + return Y.Dom.getElementsBy(method, tag, root); + }, + + /** + * Determines whether an HTMLElement has the given className. + * @method hasClass + * @param {String | HTMLElement | Array} el The element or collection to test + * @param {String} className the class name to search for + * @return {Boolean | Array} A boolean value or array of boolean values + */ + hasClass: function(el, className) { + var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)'); + + var f = function(el) { + return re.test(el['className']); + }; + + return Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Adds a class name to a given element or collection of elements. + * @method addClass + * @param {String | HTMLElement | Array} el The element or collection to add the class to + * @param {String} className the class name to add to the class attribute + */ + addClass: function(el, className) { + var f = function(el) { + if (this.hasClass(el, className)) { return; } // already present + + + el['className'] = [el['className'], className].join(' '); + }; + + Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Removes a class name from a given element or collection of elements. + * @method removeClass + * @param {String | HTMLElement | Array} el The element or collection to remove the class from + * @param {String} className the class name to remove from the class attribute + */ + removeClass: function(el, className) { + var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)', 'g'); + + var f = function(el) { + if (!this.hasClass(el, className)) { return; } // not present + + + var c = el['className']; + el['className'] = c.replace(re, ' '); + if ( this.hasClass(el, className) ) { // in case of multiple adjacent + this.removeClass(el, className); + } + + }; + + Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Replace a class with another class for a given element or collection of elements. + * If no oldClassName is present, the newClassName is simply added. + * @method replaceClass + * @param {String | HTMLElement | Array} el The element or collection to remove the class from + * @param {String} oldClassName the class name to be replaced + * @param {String} newClassName the class name that will be replacing the old class name + */ + replaceClass: function(el, oldClassName, newClassName) { + if (oldClassName === newClassName) { // avoid infinite loop + return false; + } + + var re = new RegExp('(?:^|\\s+)' + oldClassName + '(?:\\s+|$)', 'g'); + + var f = function(el) { + + if ( !this.hasClass(el, oldClassName) ) { + this.addClass(el, newClassName); // just add it if nothing to replace + return; // note return + } + + el['className'] = el['className'].replace(re, ' ' + newClassName + ' '); + + if ( this.hasClass(el, oldClassName) ) { // in case of multiple adjacent + this.replaceClass(el, oldClassName, newClassName); + } + }; + + Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Generates a unique ID + * @method generateId + * @param {String | HTMLElement | Array} el (optional) An optional element array of elements to add an ID to (no ID is added if one is already present). + * @param {String} prefix (optional) an optional prefix to use (defaults to "yui-gen"). + * @return {String | Array} The generated ID, or array of generated IDs (or original ID if already present on an element) + */ + generateId: function(el, prefix) { + prefix = prefix || 'yui-gen'; + el = el || {}; + + var f = function(el) { + if (el) { + el = Y.Dom.get(el); + } else { + el = {}; // just generating ID in this case + } + + if (!el.id) { + el.id = prefix + id_counter++; + } // dont override existing + + + return el.id; + }; + + return Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Determines whether an HTMLElement is an ancestor of another HTML element in the DOM hierarchy. + * @method isAncestor + * @param {String | HTMLElement} haystack The possible ancestor + * @param {String | HTMLElement} needle The possible descendent + * @return {Boolean} Whether or not the haystack is an ancestor of needle + */ + isAncestor: function(haystack, needle) { + haystack = Y.Dom.get(haystack); + if (!haystack || !needle) { return false; } + + var f = function(needle) { + if (haystack.contains && !isSafari) { // safari "contains" is broken + return haystack.contains(needle); + } + else if ( haystack.compareDocumentPosition ) { + return !!(haystack.compareDocumentPosition(needle) & 16); + } + else { // loop up and test each parent + var parent = needle.parentNode; + + while (parent) { + if (parent == haystack) { + return true; + } + else if (!parent.tagName || parent.tagName.toUpperCase() == 'HTML') { + return false; + } + + parent = parent.parentNode; + } + return false; + } + }; + + return Y.Dom.batch(needle, f, Y.Dom, true); + }, + + /** + * Determines whether an HTMLElement is present in the current document. + * @method inDocument + * @param {String | HTMLElement} el The element to search for + * @return {Boolean} Whether or not the element is present in the current document + */ + inDocument: function(el) { + var f = function(el) { + return this.isAncestor(document.documentElement, el); + }; + + return Y.Dom.batch(el, f, Y.Dom, true); + }, + + /** + * Returns a array of HTMLElements that pass the test applied by supplied boolean method. + * For optimized performance, include a tag and/or root node when possible. + * @method getElementsBy + * @param {Function} method - A boolean method for testing elements which receives the element as its only argument. + + * @param {String} tag (optional) The tag name of the elements being collected + * @param {String | HTMLElement} root (optional) The HTMLElement or an ID to use as the starting point + */ + getElementsBy: function(method, tag, root) { + tag = tag || '*'; + + var nodes = []; + + if (root) { + root = Y.Dom.get(root); + if (!root) { // if no root node, then no children + return nodes; + } + } else { + root = document; + } + + var elements = root.getElementsByTagName(tag); + + if ( !elements.length && (tag == '*' && root.all) ) { + elements = root.all; // IE < 6 + } + + for (var i = 0, len = elements.length; i < len; ++i) { + if ( method(elements[i]) ) { nodes[nodes.length] = elements[i]; } + } + + + return nodes; + }, + + /** + * Returns an array of elements that have had the supplied method applied. + * The method is called with the element(s) as the first arg, and the optional param as the second ( method(el, o) ). + * @method batch + * @param {String | HTMLElement | Array} el (optional) An element or array of elements to apply the method to + * @param {Function} method The method to apply to the element(s) + * @param {Any} o (optional) An optional arg that is passed to the supplied method + * @param {Boolean} override (optional) Whether or not to override the scope of "method" with "o" + * @return {HTMLElement | Array} The element(s) with the method applied + */ + batch: function(el, method, o, override) { + var id = el; + el = Y.Dom.get(el); + + var scope = (override) ? o : window; + + if (!el || el.tagName || !el.length) { // is null or not a collection (tagName for SELECT and others that can be both an element and a collection) + if (!el) { + return false; + } + return method.call(scope, el, o); + } + + var collection = []; + + for (var i = 0, len = el.length; i < len; ++i) { + if (!el[i]) { + id = el[i]; + } + collection[collection.length] = method.call(scope, el[i], o); + } + + return collection; + }, + + /** + * Returns the height of the document. + * @method getDocumentHeight + * @return {Int} The height of the actual document (which includes the body and its margin). + */ + getDocumentHeight: function() { + var scrollHeight = (document.compatMode != 'CSS1Compat') ? document.body.scrollHeight : document.documentElement.scrollHeight; + + var h = Math.max(scrollHeight, Y.Dom.getViewportHeight()); + return h; + }, + + /** + * Returns the width of the document. + * @method getDocumentWidth + * @return {Int} The width of the actual document (which includes the body and its margin). + */ + getDocumentWidth: function() { + var scrollWidth = (document.compatMode != 'CSS1Compat') ? document.body.scrollWidth : document.documentElement.scrollWidth; + var w = Math.max(scrollWidth, Y.Dom.getViewportWidth()); + return w; + }, + + /** + * Returns the current height of the viewport. + * @method getViewportHeight + * @return {Int} The height of the viewable area of the page (excludes scrollbars). + */ + getViewportHeight: function() { + var height = self.innerHeight; // Safari, Opera + var mode = document.compatMode; + + if ( (mode || isIE) && !isOpera ) { // IE, Gecko + height = (mode == 'CSS1Compat') ? + document.documentElement.clientHeight : // Standards + document.body.clientHeight; // Quirks + } + + return height; + }, + + /** + * Returns the current width of the viewport. + * @method getViewportWidth + * @return {Int} The width of the viewable area of the page (excludes scrollbars). + */ + + getViewportWidth: function() { + var width = self.innerWidth; // Safari + var mode = document.compatMode; + + if (mode || isIE) { // IE, Gecko, Opera + width = (mode == 'CSS1Compat') ? + document.documentElement.clientWidth : // Standards + document.body.clientWidth; // Quirks + } + return width; + } + }; +})(); +/** + * A region is a representation of an object on a grid. It is defined + * by the top, right, bottom, left extents, so is rectangular by default. If + * other shapes are required, this class could be extended to support it. + * @namespace YAHOO.util + * @class Region + * @param {Int} t the top extent + * @param {Int} r the right extent + * @param {Int} b the bottom extent + * @param {Int} l the left extent + * @constructor + */ +YAHOO.util.Region = function(t, r, b, l) { + + /** + * The region's top extent + * @property top + * @type Int + */ + this.top = t; + + /** + * The region's top extent as index, for symmetry with set/getXY + * @property 1 + * @type Int + */ + this[1] = t; + + /** + * The region's right extent + * @property right + * @type int + */ + this.right = r; + + /** + * The region's bottom extent + * @property bottom + * @type Int + */ + this.bottom = b; + + /** + * The region's left extent + * @property left + * @type Int + */ + this.left = l; + + /** + * The region's left extent as index, for symmetry with set/getXY + * @property 0 + * @type Int + */ + this[0] = l; +}; + +/** + * Returns true if this region contains the region passed in + * @method contains + * @param {Region} region The region to evaluate + * @return {Boolean} True if the region is contained with this region, + * else false + */ +YAHOO.util.Region.prototype.contains = function(region) { + return ( region.left >= this.left && + region.right <= this.right && + region.top >= this.top && + region.bottom <= this.bottom ); + +}; + +/** + * Returns the area of the region + * @method getArea + * @return {Int} the region's area + */ +YAHOO.util.Region.prototype.getArea = function() { + return ( (this.bottom - this.top) * (this.right - this.left) ); +}; + +/** + * Returns the region where the passed in region overlaps with this one + * @method intersect + * @param {Region} region The region that intersects + * @return {Region} The overlap region, or null if there is no overlap + */ +YAHOO.util.Region.prototype.intersect = function(region) { + var t = Math.max( this.top, region.top ); + var r = Math.min( this.right, region.right ); + var b = Math.min( this.bottom, region.bottom ); + var l = Math.max( this.left, region.left ); + + if (b >= t && r >= l) { + return new YAHOO.util.Region(t, r, b, l); + } else { + return null; + } +}; + +/** + * Returns the region representing the smallest region that can contain both + * the passed in region and this region. + * @method union + * @param {Region} region The region that to create the union with + * @return {Region} The union region + */ +YAHOO.util.Region.prototype.union = function(region) { + var t = Math.min( this.top, region.top ); + var r = Math.max( this.right, region.right ); + var b = Math.max( this.bottom, region.bottom ); + var l = Math.min( this.left, region.left ); + + return new YAHOO.util.Region(t, r, b, l); +}; + +/** + * toString + * @method toString + * @return string the region properties + */ +YAHOO.util.Region.prototype.toString = function() { + return ( "Region {" + + "top: " + this.top + + ", right: " + this.right + + ", bottom: " + this.bottom + + ", left: " + this.left + + "}" ); +}; + +/** + * Returns a region that is occupied by the DOM element + * @method getRegion + * @param {HTMLElement} el The element + * @return {Region} The region that the element occupies + * @static + */ +YAHOO.util.Region.getRegion = function(el) { + var p = YAHOO.util.Dom.getXY(el); + + var t = p[1]; + var r = p[0] + el.offsetWidth; + var b = p[1] + el.offsetHeight; + var l = p[0]; + + return new YAHOO.util.Region(t, r, b, l); +}; + +///////////////////////////////////////////////////////////////////////////// + +/** + * A point is a region that is special in that it represents a single point on + * the grid. + * @namespace YAHOO.util + * @class Point + * @param {Int} x The X position of the point + * @param {Int} y The Y position of the point + * @constructor + * @extends YAHOO.util.Region + */ +YAHOO.util.Point = function(x, y) { + if (x instanceof Array) { // accept output from Dom.getXY + y = x[1]; + x = x[0]; + } + + /** + * The X position of the point, which is also the right, left and index zero (for Dom.getXY symmetry) + * @property x + * @type Int + */ + + this.x = this.right = this.left = this[0] = x; + + /** + * The Y position of the point, which is also the top, bottom and index one (for Dom.getXY symmetry) + * @property y + * @type Int + */ + this.y = this.top = this.bottom = this[1] = y; +}; + +YAHOO.util.Point.prototype = new YAHOO.util.Region(); + diff --git a/source/web/scripts/ajax/yahoo/event/event-min.js b/source/web/scripts/ajax/yahoo/event/event-min.js index 142a99735e..1bb595a739 100644 --- a/source/web/scripts/ajax/yahoo/event/event-min.js +++ b/source/web/scripts/ajax/yahoo/event/event-min.js @@ -1 +1,69 @@ -/* Copyright (c) 2006, Yahoo! Inc. All rights reserved.Code licensed under the BSD License:http://developer.yahoo.net/yui/license.txt version: 0.12.0 */ YAHOO.util.CustomEvent=function(_1,_2,_3,_4){this.type=_1;this.scope=_2||window;this.silent=_3;this.signature=_4||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var _5="_YUICEOnSubscribe";if(_1!==_5){this.subscribeEvent=new YAHOO.util.CustomEvent(_5,this,true);}};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(fn,_7,_8){if(this.subscribeEvent){this.subscribeEvent.fire(fn,_7,_8);}this.subscribers.push(new YAHOO.util.Subscriber(fn,_7,_8));},unsubscribe:function(fn,_9){var _10=false;for(var i=0,len=this.subscribers.length;i0){_17=_14[0];}ret=s.fn.call(_16,_17,s.obj);}else{ret=s.fn.call(_16,this.type,_14,s.obj);}if(false===ret){if(!this.silent){}return false;}}}return true;},unsubscribeAll:function(){for(var i=0,len=this.subscribers.length;i=0){_60=_23[_61];}if(!el||!_60){return false;}if(this.useLegacyEvent(el,_59)){var _62=this.getLegacyIndex(el,_59);var _63=_26[_62];if(_63){for(i=0,len=_63.length;i0);}var _76=[];for(var i=0,len=_28.length;i0){for(var i=0,len=_23.length;i0){j=_23.length;while(j){index=j-1;l=_23[index];if(l){EU.removeListener(l[EU.EL],l[EU.TYPE],l[EU.FN],index);}j=j-1;}l=null;EU.clearCache();}for(i=0,len=_25.length;i0){param=args[0];} +ret=s.fn.call(scope,param,s.obj);}else{ret=s.fn.call(scope,this.type,args,s.obj);} +if(false===ret){if(!this.silent){} +return false;}}} +return true;},unsubscribeAll:function(){for(var i=0,len=this.subscribers.length;i=0){cacheItem=listeners[index];} +if(!el||!cacheItem){return false;} +if(this.useLegacyEvent(el,sType)){var legacyIndex=this.getLegacyIndex(el,sType);var llist=legacyHandlers[legacyIndex];if(llist){for(i=0,len=llist.length;i0);} +var notAvail=[];for(var i=0,len=onAvailStack.length;i0){for(var i=0,len=listeners.length;i0){j=listeners.length;while(j){index=j-1;l=listeners[index];if(l){EU.removeListener(l[EU.EL],l[EU.TYPE],l[EU.FN],index);} +j=j-1;} +l=null;EU.clearCache();} +for(i=0,len=legacyEvents.length;i - *
  • YAHOO.util.CustomEvent.LIST: - *
      - *
    • param1: event name
    • - *
    • param2: array of arguments sent to fire
    • - *
    • param3: a custom object supplied by the subscriber
    • - *
    - *
  • - *
  • YAHOO.util.CustomEvent.FLAT - *
      - *
    • param1: the first argument passed to fire. If you need to - * pass multiple parameters, use and array or object literal
    • - *
    • param2: a custom object supplied by the subscriber
    • - *
    - *
  • - * - * @property signature - * @type int - */ - this.signature = signature || YAHOO.util.CustomEvent.LIST; - - /** - * The subscribers to this event - * @property subscribers - * @type Subscriber[] - */ - this.subscribers = []; - - if (!this.silent) { - } - - var onsubscribeType = "_YUICEOnSubscribe"; - - // Only add subscribe events for events that are not generated by - // CustomEvent - if (type !== onsubscribeType) { - - /** - * Custom events provide a custom event that fires whenever there is - * a new subscriber to the event. This provides an opportunity to - * handle the case where there is a non-repeating event that has - * already fired has a new subscriber. - * - * @event subscribeEvent - * @type YAHOO.util.CustomEvent - * @param {Function} fn The function to execute - * @param {Object} obj An object to be passed along when the event - * fires - * @param {boolean|Object} override If true, the obj passed in becomes - * the execution scope of the listener. - * if an object, that object becomes the - * the execution scope. - */ - this.subscribeEvent = - new YAHOO.util.CustomEvent(onsubscribeType, this, true); - - } -}; - -/** - * Subscriber listener sigature constant. The LIST type returns three - * parameters: the event type, the array of args passed to fire, and - * the optional custom object - * @property YAHOO.util.CustomEvent.LIST - * @static - * @type int - */ -YAHOO.util.CustomEvent.LIST = 0; - -/** - * Subscriber listener sigature constant. The FLAT type returns two - * parameters: the first argument passed to fire and the optional - * custom object - * @property YAHOO.util.CustomEvent.FLAT - * @static - * @type int - */ -YAHOO.util.CustomEvent.FLAT = 1; - -YAHOO.util.CustomEvent.prototype = { - - /** - * Subscribes the caller to this event - * @method subscribe - * @param {Function} fn The function to execute - * @param {Object} obj An object to be passed along when the event - * fires - * @param {boolean|Object} override If true, the obj passed in becomes - * the execution scope of the listener. - * if an object, that object becomes the - * the execution scope. - */ - subscribe: function(fn, obj, override) { - if (this.subscribeEvent) { - this.subscribeEvent.fire(fn, obj, override); - } - - this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, override) ); - }, - - /** - * Unsubscribes the caller from this event - * @method unsubscribe - * @param {Function} fn The function to execute - * @param {Object} obj The custom object passed to subscribe (optional) - * @return {boolean} True if the subscriber was found and detached. - */ - unsubscribe: function(fn, obj) { - var found = false; - for (var i=0, len=this.subscribers.length; i - *
  • The type of event
  • - *
  • All of the arguments fire() was executed with as an array
  • - *
  • The custom object (if any) that was passed into the subscribe() - * method
  • - * - * @method fire - * @param {Object*} arguments an arbitrary set of parameters to pass to - * the handler. - */ - fire: function() { - var len=this.subscribers.length; - if (!len && this.silent) { - return true; - } - - var args=[], ret=true, i; - - for (i=0; i 0) { - param = args[0]; - } - ret = s.fn.call(scope, param, s.obj); - } else { - ret = s.fn.call(scope, this.type, args, s.obj); - } - if (false === ret) { - if (!this.silent) { - } - - //break; - return false; - } - } - } - - return true; - }, - - /** - * Removes all listeners - * @method unsubscribeAll - */ - unsubscribeAll: function() { - for (var i=0, len=this.subscribers.length; i