You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2008/06/22 17:28:01 UTC
svn commit: r670371 [3/4] - in /struts/struts2/trunk/plugins/dojo/src:
main/resources/org/apache/struts2/static/dojo/
main/resources/org/apache/struts2/static/dojo/struts/widget/ profile/
Modified: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js?rev=670371&r1=670370&r2=670371&view=diff
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js (original)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/struts_dojo.js.uncompressed.js Sun Jun 22 08:28:01 2008
@@ -1,13 +1,3 @@
-/*
- Copyright (c) 2004-2006, The Dojo Foundation
- All Rights Reserved.
-
- Licensed under the Academic Free License version 2.1 or above OR the
- modified BSD license. For more information on Dojo licensing, see:
-
- http://dojotoolkit.org/community/licensing.shtml
-*/
-
if(typeof dojo == "undefined"){
// TODOC: HOW TO DOC THE BELOW?
@@ -921,7 +911,7 @@
//These two functions are placed outside of preloadLocalizations
//So that the xd loading can use/override them.
-dojo.hostenv.localesGenerated /***BUILD:localesGenerated***/; // value will be inserted here at build time, if necessary
+dojo.hostenv.localesGenerated =["ROOT","es-es","es","it-it","pt-br","de","fr-fr","zh-cn","pt","en-us","zh","fr","zh-tw","it","en-gb","xx","de-de","ko-kr","ja-jp","ko","en","ja"]; // value will be inserted here at build time, if necessary
dojo.hostenv.registerNlsPrefix = function(){
// summary:
@@ -22087,3 +22077,9277 @@
}
);
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+dojo.provide("struts.widget.Bind");
+
+
+
+
+
+dojo.widget.defineWidget(
+ "struts.widget.Bind",
+ dojo.widget.HtmlWidget, {
+ widgetType : "Bind",
+ executeScripts : false,
+ scriptSeparation : false,
+ targets : "",
+ targetsArray : null,
+ href : "",
+ handler : "",
+
+ //messages
+ loadingText : "Loading...",
+ errorText : "",
+ showError : true,
+ showLoading : false,
+
+ //pub/sub events
+ listenTopics : "",
+ notifyTopics : "",
+ notifyTopicsArray : null,
+ beforeNotifyTopics : "",
+ beforeNotifyTopicsArray : null,
+ afterNotifyTopics : "",
+ afterNotifyTopicsArray : null,
+ errorNotifyTopics : "",
+ errorNotifyTopicsArray : null,
+
+ formId : "",
+ formFilter : "",
+ formNode : null,
+
+ events : "",
+ indicator : "",
+
+ parseContent : true,
+
+ highlightColor : "",
+ highlightDuration : 2000,
+
+ validate : false,
+ ajaxAfterValidation : false,
+
+ //used for scripts downloading & caching
+ cacheContent : true,
+ //run script on its own scope
+ scriptSeparation : true,
+ //scope for the cript separation
+ scriptScope : null,
+ transport : "",
+
+ postCreate : function() {
+ var self = this;
+
+ //attach listeners
+ if(!dojo.string.isBlank(this.listenTopics)) {
+ this.log("Listening to " + this.listenTopics + " to refresh");
+ var topics = this.listenTopics.split(",");
+ if(topics) {
+ dojo.lang.forEach(topics, function(topic){
+ dojo.event.topic.subscribe(topic, self, "reloadContents");
+ });
+ }
+ }
+
+ //topics
+ if(!dojo.string.isBlank(this.notifyTopics)) {
+ this.notifyTopicsArray = this.notifyTopics.split(",");
+ }
+
+ //before topics
+ if(!dojo.string.isBlank(this.beforeNotifyTopics)) {
+ this.beforeNotifyTopicsArray = this.beforeNotifyTopics.split(",");
+ }
+
+ //after topics
+ if(!dojo.string.isBlank(this.afterNotifyTopics)) {
+ this.afterNotifyTopicsArray = this.afterNotifyTopics.split(",");
+ }
+
+ //error topics
+ if(!dojo.string.isBlank(this.errorNotifyTopics)) {
+ this.errorNotifyTopicsArray = this.errorNotifyTopics.split(",");
+ }
+
+ if(!dojo.string.isBlank(this.targets)) {
+ //split targets
+ this.targetsArray = this.targets.split(",");
+ }
+
+ if(!dojo.string.isBlank(this.events)) {
+ var eventsArray = this.events.split(",");
+ if(eventsArray && this.domNode) {
+ dojo.lang.forEach(eventsArray, function(event){
+ dojo.event.connect(self.domNode, event, function(evt) {
+ evt.preventDefault();
+ evt.stopPropagation();
+ self.reloadContents();
+ });
+ });
+ }
+ }
+
+ if(dojo.string.isBlank(this.formId)) {
+ //no formId, see if we are inside a form
+ this.formNode = dojo.dom.getFirstAncestorByTag(this.domNode, "form");
+ } else {
+ this.formNode = dojo.byId(this.formId);
+ }
+
+ if(this.formNode && dojo.string.isBlank(this.href)) {
+ this.href = this.formNode.action;
+ }
+ },
+
+ highlight : function() {
+ if(!dojo.string.isBlank(this.highlightColor)) {
+ var nodes = [];
+ //add nodes to array
+ dojo.lang.forEach(this.targetsArray, function(target) {
+ var node = dojo.byId(target);
+ if(node) {
+ nodes.push(node);
+ }
+ });
+ var effect = dojo.lfx.html.highlight(nodes, this.highlightColor, this.highlightDuration);
+ effect.play();
+ }
+ },
+
+ log : function(text) {
+ dojo.debug("[" + (this.widgetId ? this.widgetId : "unknown") + "] " + text);
+ },
+
+ setContent : function(text) {
+ if(this.targetsArray) {
+ var self = this;
+ var xmlParser = new dojo.xml.Parse();
+ dojo.lang.forEach(this.targetsArray, function(target) {
+ var node = dojo.byId(target);
+ if(node) {
+ node.innerHTML = text;
+
+ if(self.parseContent && text != self.loadingText){
+ var frag = xmlParser.parseElement(node, null, true);
+ dojo.widget.getParser().createSubComponents(frag, dojo.widget.byId(target));
+ }
+ } else {
+ self.log("Unable to find target: " + node);
+ }
+ });
+ }
+ },
+
+ bindHandler : function(type, data, e) {
+ //hide indicator
+ dojo.html.hide(this.indicator);
+
+ //publish topics
+ this.notify(data, type, e);
+
+ if(type == "load") {
+ if(this.validate) {
+ StrutsUtils.clearValidationErrors(this.formNode);
+ //validation is active for this action
+ var errors = StrutsUtils.getValidationErrors(data);
+ if(errors && errors.fieldErrors) {
+ //validation failed
+ StrutsUtils.showValidationErrors(this.formNode, errors);
+ return;
+ } else {
+ //validation passed
+ if(!this.ajaxAfterValidation && this.formNode) {
+ //regular submit
+ this.formNode.submit();
+ return;
+ }
+ }
+ }
+
+ // no validation or validation passed
+ if(this.executeScripts) {
+ //parse text to extract content and javascript
+ var parsed = this.parse(data);
+
+ //update targets content
+ this.setContent(parsed.text);
+
+ //execute scripts
+ this._executeScripts(parsed.scripts);
+ }
+ else {
+ this.setContent(data);
+ }
+ this.highlight();
+ } else {
+ if(this.showError) {
+ var message = dojo.string.isBlank(this.errorText) ? e.message : this.errorText;
+ this.setContent(message);
+ }
+ }
+ },
+
+ notify : function(data, type, e) {
+ var self = this;
+ //general topics
+ if(this.notifyTopicsArray) {
+ dojo.lang.forEach(this.notifyTopicsArray, function(topic) {
+ try {
+ dojo.event.topic.publish(topic, data, type, e, self);
+ } catch(ex){
+ self.log(ex);
+ }
+ });
+ }
+
+ //before, after and error topics
+ var topicsArray = null;
+ switch(type) {
+ case "before":
+ this.notifyTo(this.beforeNotifyTopicsArray, null, e);
+ break;
+ case "load":
+ this.notifyTo(this.afterNotifyTopicsArray, data, e);
+ break;
+ case "error":
+ this.notifyTo(this.errorNotifyTopicsArray, data, e);
+ break;
+ }
+ },
+
+ notifyTo : function(topicsArray, data, e) {
+ var self = this;
+ if(topicsArray) {
+ dojo.lang.forEach(topicsArray, function(topic) {
+ try {
+ if(data != null) {
+ dojo.event.topic.publish(topic, data, e, self);
+ } else {
+ dojo.event.topic.publish(topic, e, self);
+ }
+ } catch(ex){
+ self.log(ex);
+ }
+ });
+ }
+ },
+
+ onDownloadStart : function(event) {
+ if(this.showLoading && !dojo.string.isBlank(this.loadingText)) {
+ event.text = this.loadingText;
+ }
+ },
+
+ reloadContents : function(evt) {
+ if(!dojo.string.isBlank(this.handler)) {
+ //use custom handler
+ this.log("Invoking handler: " + this.handler);
+ window[this.handler](this, this.domNode);
+ }
+ else {
+ try {
+ var self = this;
+ var request = {cancel: false};
+ this.notify(this.widgetId, "before", request);
+ if(request.cancel) {
+ this.log("Request canceled");
+ return;
+ }
+
+ //if the href is null, we still publish the notify topics
+ if(dojo.string.isBlank(this.href)) {
+ return;
+ }
+
+ //if there is a parent form, and it has a "onsubmit"
+ //execute it, validation is usually there, except is validation == true
+ //on which case it is ajax validation, instead of client side
+ if(!this.validate && this.formNode && this.formNode.onsubmit != null) {
+ var makeRequest = this.formNode.onsubmit.call(evt);
+ if(makeRequest != null && !makeRequest) {
+ this.log("Request canceled by 'onsubmit' of the form");
+ return;
+ }
+ }
+
+ //show indicator
+ dojo.html.show(this.indicator);
+ if(this.showLoading) {
+ this.setContent(this.loadingText);
+ }
+
+ var tmpHref = this.href;
+ tmpHref = tmpHref + (tmpHref.indexOf("?") > -1 ? "&" : "?") + "struts.enableJSONValidation=true";
+ if(!this.ajaxAfterValidation && this.validate) {
+ tmpHref = tmpHref + (tmpHref.indexOf("?") > -1 ? "&" : "?") + "struts.validateOnly=true";
+ }
+
+ if(dojo.dom.isTag(this.domNode, "INPUT", "input")
+ && this.events == "onclick"
+ && this.domNode.type == "submit"
+ && !dojo.string.isBlank(this.domNode.name)
+ && !dojo.string.isBlank(this.domNode.value)) {
+ var enc = /utf/i.test("") ? encodeURIComponent : dojo.string.encodeAscii
+ tmpHref = tmpHref + (tmpHref.indexOf("?") > -1 ? "&" : "?") + enc(this.domNode.name) + "=" + enc(this.domNode.value);
+ }
+
+ dojo.io.bind({
+ url: tmpHref,
+ useCache: false,
+ preventCache: true,
+ formNode: self.formNode,
+ formFilter: window[self.formFilter],
+ transport: self.transport,
+ handler: function(type, data, e) {
+ dojo.lang.hitch(self, "bindHandler")(type, data, e);
+ },
+ mimetype: "text/html"
+ });
+ }
+ catch(ex) {
+ if(this.showError) {
+ var message = dojo.string.isBlank(this.errorText) ? ex : this.errorText;
+ this.setContent(message);
+ }
+ }
+ }
+ },
+
+ //from Dojo's ContentPane
+ parse : function(s) {
+ this.log("Parsing: " + s);
+ var match = [];
+ var tmp = [];
+ var scripts = [];
+ while(match){
+ match = s.match(/<script([^>]*)>([\s\S]*?)<\/script>/i);
+ if(!match){ break; }
+ if(match[1]){
+ attr = match[1].match(/src=(['"]?)([^"']*)\1/i);
+ if(attr){
+ // remove a dojo.js or dojo.js.uncompressed.js from remoteScripts
+ // we declare all files with dojo.js as bad, regardless of folder
+ var tmp2 = attr[2].search(/.*(\bdojo\b(?:\.uncompressed)?\.js)$/);
+ if(tmp2 > -1){
+ this.log("Security note! inhibit:"+attr[2]+" from beeing loaded again.");
+ }
+ }
+ }
+ if(match[2]){
+ // strip out all djConfig variables from script tags nodeValue
+ // this is ABSOLUTLY needed as reinitialize djConfig after dojo is initialised
+ // makes a dissaster greater than Titanic, update remove writeIncludes() to
+ var sc = match[2].replace(/(?:var )?\bdjConfig\b(?:[\s]*=[\s]*\{[^}]+\}|\.[\w]*[\s]*=[\s]*[^;\n]*)?;?|dojo\.hostenv\.writeIncludes\(\s*\);?/g, "");
+ if(!sc){ continue; }
+
+ // cut out all dojo.require (...) calls, if we have execute
+ // scripts false widgets dont get there require calls
+ // does suck out possible widgetpackage registration as well
+ tmp = [];
+ while(tmp){
+ tmp = sc.match(/dojo\.(?:(?:require(?:After)?(?:If)?)|(?:widget\.(?:manager\.)?registerWidgetPackage)|(?:(?:hostenv\.)?setModulePrefix))\((['"]).*?\1\)\s*;?/);
+ if(!tmp){ break;}
+ sc = sc.replace(tmp[0], "");
+ }
+ scripts.push(sc);
+ }
+ s = s.replace(/<script[^>]*>[\s\S]*?<\/script>/i, "");
+ }
+
+ return {
+ text: s,
+ scripts: scripts
+ };
+ },
+
+ //from Dojo content pane
+ _executeScripts : function (scripts) {
+ var self = this;
+ var tmp = "", code = "";
+ for (var i = 0; i < scripts.length; i++) {
+ if (scripts[i].path) {
+ dojo.io.bind(this._cacheSetting({"url":scripts[i].path, "load":function (type, scriptStr) {
+ dojo.lang.hitch(self, tmp = ";" + scriptStr);
+ }, "error":function (type, error) {
+ error.text = type + " downloading remote script";
+ self._handleDefaults.call(self, error, "onExecError", "debug");
+ }, "mimetype":"text/plain", "sync":true}, this.cacheContent));
+ code += tmp;
+ } else {
+ code += scripts[i];
+ }
+ }
+ try {
+ if (this.scriptSeparation) {
+ delete this.scriptScope;
+ this.scriptScope = new (new Function("_container_", code + "; return this;"))(self);
+ } else {
+ var djg = dojo.global();
+ if (djg.execScript) {
+ djg.execScript(code);
+ } else {
+ var djd = dojo.doc();
+ var sc = djd.createElement("script");
+ sc.appendChild(djd.createTextNode(code));
+ (this.containerNode || this.domNode).appendChild(sc);
+ }
+ }
+ }
+ catch (e) {
+ e.text = "Error running scripts from content:\n" + e.description;
+ this.log(e);
+ }
+ },
+
+ _cacheSetting : function (bindObj, useCache) {
+ for (var x in this.bindArgs) {
+ if (dojo.lang.isUndefined(bindObj[x])) {
+ bindObj[x] = this.bindArgs[x];
+ }
+ }
+ if (dojo.lang.isUndefined(bindObj.useCache)) {
+ bindObj.useCache = useCache;
+ }
+ if (dojo.lang.isUndefined(bindObj.preventCache)) {
+ bindObj.preventCache = !useCache;
+ }
+ if (dojo.lang.isUndefined(bindObj.mimetype)) {
+ bindObj.mimetype = "text/html";
+ }
+ return bindObj;
+ }
+
+});
+
+
+
+
+dojo.provide("dojo.lang.timing.Timer");
+
+
+dojo.lang.timing.Timer = function(/*int*/ interval){
+ // summary: Timer object executes an "onTick()" method repeatedly at a specified interval.
+ // repeatedly at a given interval.
+ // interval: Interval between function calls, in milliseconds.
+ this.timer = null;
+ this.isRunning = false;
+ this.interval = interval;
+
+ this.onStart = null;
+ this.onStop = null;
+};
+
+dojo.extend(dojo.lang.timing.Timer, {
+ onTick : function(){
+ // summary: Method called every time the interval passes. Override to do something useful.
+ },
+
+ setInterval : function(interval){
+ // summary: Reset the interval of a timer, whether running or not.
+ // interval: New interval, in milliseconds.
+ if (this.isRunning){
+ dj_global.clearInterval(this.timer);
+ }
+ this.interval = interval;
+ if (this.isRunning){
+ this.timer = dj_global.setInterval(dojo.lang.hitch(this, "onTick"), this.interval);
+ }
+ },
+
+ start : function(){
+ // summary: Start the timer ticking.
+ // description: Calls the "onStart()" handler, if defined.
+ // Note that the onTick() function is not called right away,
+ // only after first interval passes.
+ if (typeof this.onStart == "function"){
+ this.onStart();
+ }
+ this.isRunning = true;
+ this.timer = dj_global.setInterval(dojo.lang.hitch(this, "onTick"), this.interval);
+ },
+
+ stop : function(){
+ // summary: Stop the timer.
+ // description: Calls the "onStop()" handler, if defined.
+ if (typeof this.onStop == "function"){
+ this.onStop();
+ }
+ this.isRunning = false;
+ dj_global.clearInterval(this.timer);
+ }
+});
+
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+dojo.provide("struts.widget.BindDiv");
+
+
+
+
+
+dojo.widget.defineWidget(
+ "struts.widget.BindDiv",
+ dojo.widget.ContentPane, {
+ widgetType : "BindDiv",
+
+ //from ContentPane
+ href : "",
+ extractContent : false,
+ parseContent : false,
+ cacheContent : false,
+ refreshOnShow : false,
+ executeScripts : false,
+ preload : true,
+
+ //update times
+ updateFreq : 0,
+ delay : 0,
+ autoStart : true,
+ timer : null,
+
+ //messages
+ loadingText : "Loading...",
+ showLoading : false,
+ errorText : "",
+ showError : true,
+
+ //pub/sub events
+ listenTopics : "",
+ notifyTopics : "",
+ notifyTopicsArray : null,
+ stopTimerListenTopics : "",
+ startTimerListenTopics : "",
+ beforeNotifyTopics : "",
+ beforeNotifyTopicsArray : null,
+ afterNotifyTopics : "",
+ afterNotifyTopicsArray : null,
+ errorNotifyTopics : "",
+ errorNotifyTopicsArray : null,
+
+
+ //callbacks
+ beforeLoading : "",
+ afterLoading : "",
+
+ formId : "",
+ formFilter : "",
+
+ indicator: "",
+
+ //make dojo process the content
+ parseContent : true,
+
+ highlightColor : "",
+ highlightDuration : 2000,
+
+ //only used when inside a tabbedpanel
+ disabled : false,
+
+ transport : "",
+
+ onDownloadStart : function(event) {
+ if(!this.showLoading) {
+ event.returnValue = false;
+ return;
+ }
+ if(this.showLoading && !dojo.string.isBlank(this.loadingText)) {
+ event.text = this.loadingText;
+ }
+ },
+
+ highlight : function() {
+ if(!dojo.string.isBlank(this.highlightColor)) {
+ var effect = dojo.lfx.html.highlight([this.domNode], this.highlightColor, this.highlightDuration);
+ effect.play();
+ }
+ },
+
+ onDownloadError : function(event) {
+ this.onError(event);
+ },
+
+ onContentError : function(event) {
+ this.onError(event);
+ },
+
+ onExecError : function(event) {
+ this.onError(event);
+ },
+
+ onError : function(event) {
+ if(this.showError) {
+ if(!dojo.string.isBlank(this.errorText)) {
+ event.text = this.errorText;
+ }
+ } else {
+ event.text = "";
+ }
+ },
+
+ notify : function(data, type, e) {
+ if(this.notifyTopicsArray) {
+ var self = this;
+ dojo.lang.forEach(this.notifyTopicsArray, function(topic) {
+ try {
+ dojo.event.topic.publish(topic, data, type, e, self);
+ } catch(ex) {
+ self.log(ex);
+ }
+ });
+ }
+
+ //before, after and error topics
+ var topicsArray = null;
+ switch(type) {
+ case "before":
+ this.notifyTo(this.beforeNotifyTopicsArray, null, e);
+ break;
+ case "load":
+ this.notifyTo(this.afterNotifyTopicsArray, data, e);
+ break;
+ case "error":
+ this.notifyTo(this.errorNotifyTopicsArray, data, e);
+ break;
+ }
+ },
+
+ notifyTo : function(topicsArray, data, e) {
+ var self = this;
+ if(topicsArray) {
+ dojo.lang.forEach(topicsArray, function(topic) {
+ try {
+ if(data != null) {
+ dojo.event.topic.publish(topic, data, e, self);
+ } else {
+ dojo.event.topic.publish(topic, e, self);
+ }
+ } catch(ex){
+ self.log(ex);
+ }
+ });
+ }
+ },
+
+ postCreate : function(args, frag) {
+ if (this.handler !== "") {
+ this.setHandler(this.handler);
+ }
+
+ var self = this;
+ var hitchedRefresh = function() {
+ dojo.lang.hitch(self, "refresh")();
+ };
+ var hitchedStartTimer = function() {
+ dojo.lang.hitch(self, "startTimer")();
+ };
+
+ if(this.updateFreq > 0) {
+ //there is a timer
+ this.timer = new dojo.lang.timing.Timer(this.updateFreq);
+ this.timer.onTick = hitchedRefresh;
+
+ if(this.autoStart) {
+ //start the timer
+ if(this.delay > 0) {
+ //start time after delay
+ dojo.lang.setTimeout(hitchedStartTimer, this.delay);
+ } else {
+ //start timer now
+ this.startTimer();
+ }
+ }
+ } else {
+ //no timer
+ if(this.delay > 0) {
+ //load after delay
+ dojo.lang.setTimeout(hitchedRefresh, this.delay);
+ }
+ }
+
+ //attach listeners
+ if(!dojo.string.isBlank(this.listenTopics)) {
+ this.log("Listening to " + this.listenTopics + " to refresh");
+ var topics = this.listenTopics.split(",");
+ if(topics) {
+ dojo.lang.forEach(topics, function(topic){
+ dojo.event.topic.subscribe(topic, self, "refresh");
+ });
+ }
+ }
+
+ if(!dojo.string.isBlank(this.stopTimerListenTopics)) {
+ this.log("Listening to " + this.stopTimerListenTopics + " to stop timer");
+ var stopTopics = this.stopTimerListenTopics.split(",");
+ if(stopTopics) {
+ dojo.lang.forEach(stopTopics, function(topic){
+ dojo.event.topic.subscribe(topic, self, "stopTimer");
+ });
+ }
+ }
+
+ if(!dojo.string.isBlank(this.startTimerListenTopics)) {
+ this.log("Listening to " + this.stopTimerListenTopics + " to start timer");
+ var startTopics = this.startTimerListenTopics.split(",");
+ if(startTopics) {
+ dojo.lang.forEach(startTopics, function(topic){
+ dojo.event.topic.subscribe(topic, self, "startTimer");
+ });
+ }
+ }
+
+ //notify topics
+ if(!dojo.string.isBlank(this.notifyTopics)) {
+ this.notifyTopicsArray = this.notifyTopics.split(",");
+ }
+
+ //before topics
+ if(!dojo.string.isBlank(this.beforeNotifyTopics)) {
+ this.beforeNotifyTopicsArray = this.beforeNotifyTopics.split(",");
+ }
+
+ //after topics
+ if(!dojo.string.isBlank(this.afterNotifyTopics)) {
+ this.afterNotifyTopicsArray = this.afterNotifyTopics.split(",");
+ }
+
+ //error topics
+ if(!dojo.string.isBlank(this.errorNotifyTopics)) {
+ this.errorNotifyTopicsArray = this.errorNotifyTopics.split(",");
+ }
+
+ if(this.isShowing() && this.preload && this.updateFreq <= 0 && this.delay <= 0) {
+ this.refresh();
+ }
+ },
+
+ _downloadExternalContent: function(url, useCache) {
+
+ var request = {cancel: false};
+ this.notify(this.widgetId, "before", request);
+ if(request.cancel) {
+ return;
+ }
+
+ //show indicator
+ dojo.html.show(this.indicator);
+
+ this._handleDefaults("Loading...", "onDownloadStart");
+ var self = this;
+ dojo.io.bind({
+ url: url,
+ useCache: useCache,
+ preventCache: !useCache,
+ mimetype: "text/html",
+ formNode: dojo.byId(self.formId),
+ formFilter: window[self.formFilter],
+ transport: self.transport,
+ handler: function(type, data, e) {
+ //hide indicator
+ dojo.html.hide(self.indicator);
+
+ self.notify(data, type, e);
+
+ if(type == "load") {
+ self.onDownloadEnd.call(self, url, data);
+ self.highlight();
+ } else {
+ // works best when from a live server instead of from file system
+ self._handleDefaults.call(self, "Error loading '" + url + "' (" + e.status + " "+ e.statusText + ")", "onDownloadError");
+ self.onLoad();
+ }
+ }
+ });
+ },
+
+ log : function(text) {
+ dojo.debug("[" + this.widgetId + "] " + text);
+ },
+
+ stopTimer : function() {
+ if(this.timer && this.timer.isRunning) {
+ this.log("stopping timer");
+ this.timer.stop();
+ }
+ },
+
+ startTimer : function() {
+ if(this.timer && !this.timer.isRunning) {
+ this.log("starting timer with update interval " + this.updateFreq);
+ this.timer.start();
+ }
+ },
+
+ //from Dojo's ContentPane
+ //TODO: remove when fixed on Dojo (WW-1869)
+ splitAndFixPaths:function (s, url) {
+ var titles = [], scripts = [], tmp = [];
+ var match = [], requires = [], attr = [], styles = [];
+ var str = "", path = "", fix = "", tagFix = "", tag = "", origPath = "";
+ if (!url) {
+ url = "./";
+ }
+ if (s) {
+ var regex = /<title[^>]*>([\s\S]*?)<\/title>/i;
+ while (match = regex.exec(s)) {
+ titles.push(match[1]);
+ s = s.substring(0, match.index) + s.substr(match.index + match[0].length);
+ }
+ if (this.adjustPaths) {
+ var regexFindTag = /<[a-z][a-z0-9]*[^>]*\s(?:(?:src|href|style)=[^>])+[^>]*>/i;
+ var regexFindAttr = /\s(src|href|style)=(['"]?)([\w()\[\]\/.,\\'"-:;#=&?\s@!]+?)\2/i;
+ var regexProtocols = /^(?:[#]|(?:(?:https?|ftps?|file|javascript|mailto|news):))/;
+ while (tag = regexFindTag.exec(s)) {
+ str += s.substring(0, tag.index);
+ s = s.substring((tag.index + tag[0].length), s.length);
+ tag = tag[0];
+ tagFix = "";
+ while (attr = regexFindAttr.exec(tag)) {
+ path = "";
+ origPath = attr[3];
+ switch (attr[1].toLowerCase()) {
+ case "src":
+ case "href":
+ if (regexProtocols.exec(origPath)) {
+ path = origPath;
+ } else {
+ path = (new dojo.uri.Uri(url, origPath).toString());
+ }
+ break;
+ case "style":
+ path = dojo.html.fixPathsInCssText(origPath, url);
+ break;
+ default:
+ path = origPath;
+ }
+ fix = " " + attr[1] + "=" + attr[2] + path + attr[2];
+ tagFix += tag.substring(0, attr.index) + fix;
+ tag = tag.substring((attr.index + attr[0].length), tag.length);
+ }
+ str += tagFix + tag;
+ }
+ s = str + s;
+ }
+ regex = /(?:<(style)[^>]*>([\s\S]*?)<\/style>|<link ([^>]*rel=['"]?stylesheet['"]?[^>]*)>)/i;
+ while (match = regex.exec(s)) {
+ if (match[1] && match[1].toLowerCase() == "style") {
+ styles.push(dojo.html.fixPathsInCssText(match[2], url));
+ } else {
+ if (attr = match[3].match(/href=(['"]?)([^'">]*)\1/i)) {
+ styles.push({path:attr[2]});
+ }
+ }
+ s = s.substring(0, match.index) + s.substr(match.index + match[0].length);
+ }
+ var regex = /<script([^>]*)>([\s\S]*?)<\/script>/i;
+ var regexSrc = /src=(['"]?)([^"']*)\1/i;
+ var regexDojoJs = /.*(\bdojo\b\.js(?:\.uncompressed\.js)?)$/;
+ var regexInvalid = /(?:var )?\bdjConfig\b(?:[\s]*=[\s]*\{[^}]+\}|\.[\w]*[\s]*=[\s]*[^;\n]*)?;?|dojo\.hostenv\.writeIncludes\(\s*\);?/g;
+ var regexRequires = /dojo\.(?:(?:require(?:After)?(?:If)?)|(?:widget\.(?:manager\.)?registerWidgetPackage)|(?:(?:hostenv\.)?setModulePrefix|registerModulePath)|defineNamespace)\((['"]).*?\1\)\s*;?/;
+ while (match = regex.exec(s)) {
+ if (this.executeScripts && match[1]) {
+ if (attr = regexSrc.exec(match[1])) {
+ if (regexDojoJs.exec(attr[2])) {
+ dojo.debug("Security note! inhibit:" + attr[2] + " from being loaded again.");
+ } else {
+ scripts.push({path:attr[2]});
+ }
+ }
+ }
+ if (match[2]) {
+ var sc = match[2].replace(regexInvalid, "");
+ if (!sc) {
+ continue;
+ }
+ while (tmp = regexRequires.exec(sc)) {
+ requires.push(tmp[0]);
+ sc = sc.substring(0, tmp.index) + sc.substr(tmp.index + tmp[0].length);
+ }
+ if (this.executeScripts) {
+ scripts.push(sc);
+ }
+ }
+ s = s.substr(0, match.index) + s.substr(match.index + match[0].length);
+ }
+ if (this.extractContent) {
+ match = s.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
+ if (match) {
+ s = match[1];
+ }
+ }
+ if (this.executeScripts && this.scriptSeparation) {
+ var regex = /(<[a-zA-Z][a-zA-Z0-9]*\s[^>]*?\S=)((['"])[^>]*scriptScope[^>]*>)/;
+ var regexAttr = /([\s'";:\(])scriptScope(.*)/;
+ str = "";
+ while (tag = regex.exec(s)) {
+ tmp = ((tag[3] == "'") ? "\"" : "'");
+ fix = "";
+ str += s.substring(0, tag.index) + tag[1];
+ while (attr = regexAttr.exec(tag[2])) {
+ tag[2] = tag[2].substring(0, attr.index) + attr[1] + "dojo.widget.byId(" + tmp + this.widgetId + tmp + ").scriptScope" + attr[2];
+ }
+ str += tag[2];
+ s = s.substr(tag.index + tag[0].length);
+ }
+ s = str + s;
+ }
+ }
+ return {"xml":s, "styles":styles, "titles":titles, "requires":requires, "scripts":scripts, "url":url};
+ }
+});
+
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+dojo.provide("struts.widget.BindAnchor");
+
+
+
+
+
+dojo.widget.defineWidget(
+ "struts.widget.BindAnchor",
+ struts.widget.Bind, {
+ widgetType : "BindAnchor",
+
+ events: "onclick",
+
+ postCreate : function() {
+ struts.widget.BindAnchor.superclass.postCreate.apply(this);
+ this.domNode.href = "#";
+ }
+});
+
+
+
+
+dojo.provide("dojo.widget.html.stabile");
+
+dojo.widget.html.stabile = {
+ // summary: Maintain state of widgets when user hits back/forward button
+
+ // Characters to quote in single-quoted regexprs
+ _sqQuotables: new RegExp("([\\\\'])", "g"),
+
+ // Current depth.
+ _depth: 0,
+
+ // Set to true when calling v.toString, to sniff for infinite
+ // recursion.
+ _recur: false,
+
+ // Levels of nesting of Array and object displays.
+ // If when >= depth, no display or array or object internals.
+ depthLimit: 2
+};
+
+//// PUBLIC METHODS
+
+dojo.widget.html.stabile.getState = function(id){
+ // summary
+ // Get the state stored for the widget with the given ID, or undefined
+ // if none.
+
+ dojo.widget.html.stabile.setup();
+ return dojo.widget.html.stabile.widgetState[id];
+}
+
+dojo.widget.html.stabile.setState = function(id, state, isCommit){
+ // summary
+ // Set the state stored for the widget with the given ID. If isCommit
+ // is true, commits all widget state to more stable storage.
+
+ dojo.widget.html.stabile.setup();
+ dojo.widget.html.stabile.widgetState[id] = state;
+ if(isCommit){
+ dojo.widget.html.stabile.commit(dojo.widget.html.stabile.widgetState);
+ }
+}
+
+dojo.widget.html.stabile.setup = function(){
+ // summary
+ // Sets up widgetState: a hash keyed by widgetId, maps to an object
+ // or array writable with "describe". If there is data in the widget
+ // storage area, use it, otherwise initialize an empty object.
+
+ if(!dojo.widget.html.stabile.widgetState){
+ var text = dojo.widget.html.stabile._getStorage().value;
+ dojo.widget.html.stabile.widgetState = text ? dj_eval("("+text+")") : {};
+ }
+}
+
+dojo.widget.html.stabile.commit = function(state){
+ // summary
+ // Commits all widget state to more stable storage, so if the user
+ // navigates away and returns, it can be restored.
+
+ dojo.widget.html.stabile._getStorage().value = dojo.widget.html.stabile.description(state);
+}
+
+dojo.widget.html.stabile.description = function(v, showAll){
+ // summary
+ // Return a JSON "description string" for the given value.
+ // Supports only core JavaScript types with literals, plus Date,
+ // and cyclic structures are unsupported.
+ // showAll defaults to false -- if true, this becomes a simple symbolic
+ // object dumper, but you cannot "eval" the output.
+
+ // Save and later restore dojo.widget.html.stabile._depth;
+ var depth = dojo.widget.html.stabile._depth;
+
+ var describeThis = function() {
+ return this.description(this, true);
+ }
+
+ try {
+
+ if(v===void(0)){
+ return "undefined";
+ }
+ if(v===null){
+ return "null";
+ }
+ if(typeof(v)=="boolean" || typeof(v)=="number"
+ || v instanceof Boolean || v instanceof Number){
+ return v.toString();
+ }
+
+ if(typeof(v)=="string" || v instanceof String){
+ // Quote strings and their contents as required.
+ // Replacing by $& fails in IE 5.0
+ var v1 = v.replace(dojo.widget.html.stabile._sqQuotables, "\\$1");
+ v1 = v1.replace(/\n/g, "\\n");
+ v1 = v1.replace(/\r/g, "\\r");
+ // Any other important special cases?
+ return "'"+v1+"'";
+ }
+
+ if(v instanceof Date){
+ // Create a data constructor.
+ return "new Date("+d.getFullYear+","+d.getMonth()+","+d.getDate()+")";
+ }
+
+ var d;
+ if(v instanceof Array || v.push){
+ // "push" test needed for KHTML/Safari, don't know why -cp
+
+ if(depth>=dojo.widget.html.stabile.depthLimit)
+ return "[ ... ]";
+
+ d = "[";
+ var first = true;
+ dojo.widget.html.stabile._depth++;
+ for(var i=0; i<v.length; i++){
+ // Skip functions and undefined values
+ // if(v[i]==undef || typeof(v[i])=="function")
+ // continue;
+ if(first){
+ first = false;
+ }else{
+ d += ",";
+ }
+ d+=arguments.callee(v[i], showAll);
+ }
+ return d+"]";
+ }
+
+ if(v.constructor==Object
+ || v.toString==describeThis){
+ if(depth>=dojo.widget.html.stabile.depthLimit)
+ return "{ ... }";
+
+ // Instanceof Hash is good, or if we just use Objects,
+ // we can say v.constructor==Object.
+ // IE (5?) lacks hasOwnProperty, but perhaps objects do not always
+ // have prototypes??
+ if(typeof(v.hasOwnProperty)!="function" && v.prototype){
+ throw new Error("description: "+v+" not supported by script engine");
+ }
+ var first = true;
+ d = "{";
+ dojo.widget.html.stabile._depth++;
+ for(var key in v){
+ // Skip values that are functions or undefined.
+ if(v[key]==void(0) || typeof(v[key])=="function")
+ continue;
+ if(first){
+ first = false;
+ }else{
+ d += ", ";
+ }
+ var kd = key;
+ // If the key is not a legal identifier, use its description.
+ // For strings this will quote the stirng.
+ if(!kd.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)){
+ kd = arguments.callee(key, showAll);
+ }
+ d += kd+": "+arguments.callee(v[key], showAll);
+ }
+ return d+"}";
+ }
+
+ if(showAll){
+ if(dojo.widget.html.stabile._recur){
+ // Save the original definitions of toString;
+ var objectToString = Object.prototype.toString;
+ return objectToString.apply(v, []);
+ }else{
+ dojo.widget.html.stabile._recur = true;
+ return v.toString();
+ }
+ }else{
+ // log("Description? "+v.toString()+", "+typeof(v));
+ throw new Error("Unknown type: "+v);
+ return "'unknown'";
+ }
+
+ } finally {
+ // Always restore the global current depth.
+ dojo.widget.html.stabile._depth = depth;
+ }
+
+}
+
+
+
+//// PRIVATE TO MODULE
+
+dojo.widget.html.stabile._getStorage = function(){
+ // summary
+ // Gets an object (form field) with a read/write "value" property.
+
+ if (dojo.widget.html.stabile.dataField) {
+ return dojo.widget.html.stabile.dataField;
+ }
+ var form = document.forms._dojo_form;
+ return dojo.widget.html.stabile.dataField = form ? form.stabile : {value: ""};
+}
+
+
+dojo.provide("dojo.widget.ComboBox");
+
+
+
+
+
+
+
+
+
+dojo.declare(
+ "dojo.widget.incrementalComboBoxDataProvider",
+ null,
+ function(options){
+ // summary:
+ // Reference implementation / interface for Combobox incremental data provider.
+ // This class takes a search string and returns values that match
+ // that search string. The filtering of values (to find values matching given
+ // search string) is done on the server.
+ //
+ // options:
+ // Structure containing {dataUrl: "foo.js?search={searchString}"} or similar data.
+ // dataUrl is a URL that is passed the search string a returns a JSON structure
+ // showing the matching values, like [ ["Alabama","AL"], ["Alaska","AK"], ["American Samoa","AS"] ]
+
+ this.searchUrl = options.dataUrl;
+
+ // TODO: cache doesn't work
+ this._cache = {};
+
+ this._inFlight = false;
+ this._lastRequest = null;
+
+ // allowCache: Boolean
+ // Setting to use/not use cache for previously seen values
+ // TODO: caching doesn't work.
+ // TODO: read the setting for this value from the widget parameters
+ this.allowCache = false;
+ },
+ {
+ _addToCache: function(/*String*/ keyword, /*Array*/ data){
+ if(this.allowCache){
+ this._cache[keyword] = data;
+ }
+ },
+
+ startSearch: function(/*String*/ searchStr, /*Function*/ callback){
+ // summary:
+ // Start the search for patterns that match searchStr, and call
+ // specified callback functions with the results
+ // searchStr:
+ // The characters the user has typed into the <input>.
+ // callback:
+ // This function will be called with the result, as an
+ // array of label/value pairs (the value is used for the Select widget). Example:
+ // [ ["Alabama","AL"], ["Alaska","AK"], ["American Samoa","AS"] ]
+
+ if(this._inFlight){
+ // FIXME: implement backoff!
+ }
+ var tss = encodeURIComponent(searchStr);
+ var realUrl = dojo.string.substituteParams(this.searchUrl, {"searchString": tss});
+ var _this = this;
+ var request = this._lastRequest = dojo.io.bind({
+ url: realUrl,
+ method: "get",
+ mimetype: "text/json",
+ load: function(type, data, evt){
+ _this._inFlight = false;
+ if(!dojo.lang.isArray(data)){
+ var arrData = [];
+ for(var key in data){
+ arrData.push([data[key], key]);
+ }
+ data = arrData;
+ }
+ _this._addToCache(searchStr, data);
+ if (request == _this._lastRequest){
+ callback(data);
+ }
+ }
+ });
+ this._inFlight = true;
+ }
+ }
+);
+
+dojo.declare(
+ "dojo.widget.basicComboBoxDataProvider",
+ null,
+ function(/*Object*/ options, /*DomNode*/ node){
+ // summary:
+ // Reference implementation / interface for Combobox data provider.
+ // This class takes a search string and returns values that match
+ // that search string. All possible values for the combobox are downloaded
+ // on initialization, and then startSearch() runs locally,
+ // merely filting that downloaded list, to find values matching search string
+ //
+ // NOTE: this data provider is designed as a naive reference
+ // implementation, and as such it is written more for readability than
+ // speed. A deployable data provider would implement lookups, search
+ // caching (and invalidation), and a significantly less naive data
+ // structure for storage of items.
+ //
+ // options: Object
+ // Options object. Example:
+ // {
+ // dataUrl: String (URL to query to get list of possible drop down values),
+ // setAllValues: Function (callback for setting initially selected value)
+ // }
+ // The return format for dataURL is (for example)
+ // [ ["Alabama","AL"], ["Alaska","AK"], ["American Samoa","AS"] ... ]
+ //
+ // node:
+ // Pointer to the domNode in the original markup.
+ // This is needed in the case when the list of values is embedded
+ // in the html like <select> <option>Alabama</option> <option>Arkansas</option> ...
+ // rather than specified as a URL.
+
+ // _data: Array
+ // List of every possible value for the drop down list
+ // startSearch() simply searches this array and returns matching values.
+ this._data = [];
+
+ // searchLimit: Integer
+ // Maximum number of results to return.
+ // TODO: need to read this value from the widget parameters
+ this.searchLimit = 30;
+
+ // searchType: String
+ // Defines what values match the search string; see searchType parameter
+ // of ComboBox for details
+ // TODO: need to read this value from the widget parameters; the setting in ComboBox is being ignored.
+ this.searchType = "STARTSTRING";
+
+ // caseSensitive: Boolean
+ // Should search be case sensitive?
+ // TODO: this should be a parameter to combobox?
+ this.caseSensitive = false;
+
+ if(!dj_undef("dataUrl", options) && !dojo.string.isBlank(options.dataUrl)){
+ this._getData(options.dataUrl);
+ }else{
+ // check to see if we can populate the list from <option> elements
+ if((node)&&(node.nodeName.toLowerCase() == "select")){
+ // NOTE: we're not handling <optgroup> here yet
+ var opts = node.getElementsByTagName("option");
+ var ol = opts.length;
+ var data = [];
+ for(var x=0; x<ol; x++){
+ var text = opts[x].textContent || opts[x].innerText || opts[x].innerHTML;
+ var keyValArr = [String(text), String(opts[x].value)];
+ data.push(keyValArr);
+ if(opts[x].selected){
+ options.setAllValues(keyValArr[0], keyValArr[1]);
+ }
+ }
+ this.setData(data);
+ }
+ }
+ },
+ {
+ _getData: function(/*String*/ url){
+ dojo.io.bind({
+ url: url,
+ load: dojo.lang.hitch(this, function(type, data, evt){
+ if(!dojo.lang.isArray(data)){
+ var arrData = [];
+ for(var key in data){
+ arrData.push([data[key], key]);
+ }
+ data = arrData;
+ }
+ this.setData(data);
+ }),
+ mimetype: "text/json"
+ });
+ },
+
+ startSearch: function(/*String*/ searchStr, /*Function*/ callback){
+ // summary:
+ // Start the search for patterns that match searchStr.
+ // searchStr:
+ // The characters the user has typed into the <input>.
+ // callback:
+ // This function will be called with the result, as an
+ // array of label/value pairs (the value is used for the Select widget). Example:
+ // [ ["Alabama","AL"], ["Alaska","AK"], ["American Samoa","AS"] ]
+
+ // FIXME: need to add timeout handling here!!
+ this._performSearch(searchStr, callback);
+ },
+
+ _performSearch: function(/*String*/ searchStr, /*Function*/ callback){
+ //
+ // NOTE: this search is LINEAR, which means that it exhibits perhaps
+ // the worst possible speed characteristics of any search type. It's
+ // written this way to outline the responsibilities and interfaces for
+ // a search.
+ //
+ var st = this.searchType;
+ // FIXME: this is just an example search, which means that we implement
+ // only a linear search without any of the attendant (useful!) optimizations
+ var ret = [];
+ if(!this.caseSensitive){
+ searchStr = searchStr.toLowerCase();
+ }
+ for(var x=0; x<this._data.length; x++){
+ if((this.searchLimit > 0)&&(ret.length >= this.searchLimit)){
+ break;
+ }
+ // FIXME: we should avoid copies if possible!
+ var dataLabel = new String((!this.caseSensitive) ? this._data[x][0].toLowerCase() : this._data[x][0]);
+ if(dataLabel.length < searchStr.length){
+ // this won't ever be a good search, will it? What if we start
+ // to support regex search?
+ continue;
+ }
+
+ if(st == "STARTSTRING"){
+ if(searchStr == dataLabel.substr(0, searchStr.length)){
+ ret.push(this._data[x]);
+ }
+ }else if(st == "SUBSTRING"){
+ // this one is a gimmie
+ if(dataLabel.indexOf(searchStr) >= 0){
+ ret.push(this._data[x]);
+ }
+ }else if(st == "STARTWORD"){
+ // do a substring search and then attempt to determine if the
+ // preceeding char was the beginning of the string or a
+ // whitespace char.
+ var idx = dataLabel.indexOf(searchStr);
+ if(idx == 0){
+ // implicit match
+ ret.push(this._data[x]);
+ }
+ if(idx <= 0){
+ // if we didn't match or implicily matched, march onward
+ continue;
+ }
+ // otherwise, we have to go figure out if the match was at the
+ // start of a word...
+ // this code is taken almost directy from nWidgets
+ var matches = false;
+ while(idx!=-1){
+ // make sure the match either starts whole string, or
+ // follows a space, or follows some punctuation
+ if(" ,/(".indexOf(dataLabel.charAt(idx-1)) != -1){
+ // FIXME: what about tab chars?
+ matches = true; break;
+ }
+ idx = dataLabel.indexOf(searchStr, idx+1);
+ }
+ if(!matches){
+ continue;
+ }else{
+ ret.push(this._data[x]);
+ }
+ }
+ }
+ callback(ret);
+ },
+
+ setData: function(/*Array*/ pdata){
+ // summary: set (or reset) the data and initialize lookup structures
+ this._data = pdata;
+ }
+ }
+);
+
+dojo.widget.defineWidget(
+ "dojo.widget.ComboBox",
+ dojo.widget.HtmlWidget,
+ {
+ // summary:
+ // Auto-completing text box, and base class for Select widget.
+ //
+ // The drop down box's values are populated from an class called
+ // a data provider, which returns a list of values based on the characters
+ // that the user has typed into the input box.
+ //
+ // Some of the options to the ComboBox are actually arguments to the data
+ // provider.
+
+ // forceValidOption: Boolean
+ // If true, only allow selection of strings in drop down list.
+ // If false, user can select a value from the drop down, or just type in
+ // any random value.
+ forceValidOption: false,
+
+ // searchType: String
+ // Argument to data provider.
+ // Specifies rule for matching typed in string w/list of available auto-completions.
+ // startString - look for auto-completions that start w/the specified string.
+ // subString - look for auto-completions containing the typed in string.
+ // startWord - look for auto-completions where any word starts w/the typed in string.
+ searchType: "stringstart",
+
+ // dataProvider: Object
+ // (Read only) reference to data provider object created for this combobox
+ // according to "dataProviderClass" argument.
+ dataProvider: null,
+
+ // autoComplete: Boolean
+ // If you type in a partial string, and then tab out of the <input> box,
+ // automatically copy the first entry displayed in the drop down list to
+ // the <input> field
+ autoComplete: true,
+
+ // searchDelay: Integer
+ // Delay in milliseconds between when user types something and we start
+ // searching based on that value
+ searchDelay: 100,
+
+ // dataUrl: String
+ // URL argument passed to data provider object (class name specified in "dataProviderClass")
+ // An example of the URL format for the default data provider is
+ // "remoteComboBoxData.js?search=%{searchString}"
+ dataUrl: "",
+
+ // fadeTime: Integer
+ // Milliseconds duration of fadeout for drop down box
+ fadeTime: 200,
+
+ // maxListLength: Integer
+ // Limits list to X visible rows, scroll on rest
+ maxListLength: 8,
+
+ // mode: String
+ // Mode must be specified unless dataProviderClass is specified.
+ // "local" to inline search string, "remote" for JSON-returning live search
+ // or "html" for dumber live search.
+ mode: "local",
+
+ // selectedResult: Array
+ // (Read only) array specifying the value/label that the user selected
+ selectedResult: null,
+
+ // dataProviderClass: String
+ // Name of data provider class (code that maps a search string to a list of values)
+ // The class must match the interface demonstrated by dojo.widget.incrementalComboBoxDataProvider
+ dataProviderClass: "",
+
+ // buttonSrc: URI
+ // URI for the down arrow icon to the right of the input box.
+ buttonSrc: dojo.uri.moduleUri("dojo.widget", "templates/images/combo_box_arrow.png"),
+
+ // dropdownToggle: String
+ // Animation effect for showing/displaying drop down box
+ dropdownToggle: "fade",
+
+ templateString:"<span _=\"whitespace and CR's between tags adds in FF\"\r\n\tclass=\"dojoComboBoxOuter\"\r\n\t><input style=\"display:none\" tabindex=\"-1\" name=\"\" value=\"\" \r\n\t\tdojoAttachPoint=\"comboBoxValue\"\r\n\t><input style=\"display:none\" tabindex=\"-1\" name=\"\" value=\"\" \r\n\t\tdojoAttachPoint=\"comboBoxSelectionValue\"\r\n\t><input type=\"text\" autocomplete=\"off\" class=\"dojoComboBox\"\r\n\t\tdojoAttachEvent=\"key:_handleKeyEvents; keyUp: onKeyUp; compositionEnd; onResize;\"\r\n\t\tdojoAttachPoint=\"textInputNode\"\r\n\t><img hspace=\"0\"\r\n\t\tvspace=\"0\"\r\n\t\tclass=\"dojoComboBox\"\r\n\t\tdojoAttachPoint=\"downArrowNode\"\r\n\t\tdojoAttachEvent=\"onMouseUp: handleArrowClick; onResize;\"\r\n\t\tsrc=\"${this.buttonSrc}\"\r\n></span>\r\n",
+ templateCssString:".dojoComboBoxOuter {\r\n\tborder: 0px !important;\r\n\tmargin: 0px !important;\r\n\tpadding: 0px !important;\r\n\tbackground: transparent !important;\r\n\twhite-space: nowrap !important;\r\n}\r\n\r\n.dojoComboBox {\r\n\tborder: 1px inset #afafaf;\r\n\tmargin: 0px;\r\n\tpadding: 0px;\r\n\tvertical-align: middle !important;\r\n\tfloat: none !important;\r\n\tposition: static !important;\r\n\tdisplay: inline !important;\r\n}\r\n\r\n/* the input box */\r\ninput.dojoComboBox {\r\n\tborder-right-width: 0px !important; \r\n\tmargin-right: 0px !important;\r\n\tpadding-right: 0px !important;\r\n}\r\n\r\n/* the down arrow */\r\nimg.dojoComboBox {\r\n\tborder-left-width: 0px !important;\r\n\tpadding-left: 0px !important;\r\n\tmargin-left: 0px !important;\r\n}\r\n\r\n/* IE vertical-alignment calculations can be off by +-1 but these margins are collapsed away */\r\n.dj_ie img.dojoComboBox {\r\n\tmargin-top: 1px; \r\n\tmargin-bottom: 1px; \r\n}\r\n\r\n/* the drop down
*/\r\n.dojoComboBoxOptions {\r\n\tfont-family: Verdana, Helvetica, Garamond, sans-serif;\r\n\t/* font-size: 0.7em; */\r\n\tbackground-color: white;\r\n\tborder: 1px solid #afafaf;\r\n\tposition: absolute;\r\n\tz-index: 1000; \r\n\toverflow: auto;\r\n\tcursor: default;\r\n}\r\n\r\n.dojoComboBoxItem {\r\n\tpadding-left: 2px;\r\n\tpadding-top: 2px;\r\n\tmargin: 0px;\r\n}\r\n\r\n.dojoComboBoxItemEven {\r\n\tbackground-color: #f4f4f4;\r\n}\r\n\r\n.dojoComboBoxItemOdd {\r\n\tbackground-color: white;\r\n}\r\n\r\n.dojoComboBoxItemHighlight {\r\n\tbackground-color: #63709A;\r\n\tcolor: white;\r\n}\r\n",templateCssPath: dojo.uri.moduleUri("dojo.widget", "templates/ComboBox.css"),
+
+ setValue: function(/*String*/ value){
+ // summary: Sets the value of the combobox
+ this.comboBoxValue.value = value;
+ if (this.textInputNode.value != value){ // prevent mucking up of selection
+ this.textInputNode.value = value;
+ // only change state and value if a new value is set
+ dojo.widget.html.stabile.setState(this.widgetId, this.getState(), true);
+ this.onValueChanged(value);
+ }
+ },
+
+ onValueChanged: function(/*String*/ value){
+ // summary: callback when value changes, for user to attach to
+ },
+
+ getValue: function(){
+ // summary: Rerturns combo box value
+ return this.comboBoxValue.value;
+ },
+
+ getState: function(){
+ // summary:
+ // Used for saving state of ComboBox when navigates to a new
+ // page, in case they then hit the browser's "Back" button.
+ return {value: this.getValue()};
+ },
+
+ setState: function(/*Object*/ state){
+ // summary:
+ // Used for restoring state of ComboBox when has navigated to a new
+ // page but then hits browser's "Back" button.
+ this.setValue(state.value);
+ },
+
+ enable:function(){
+ this.disabled=false;
+ this.textInputNode.removeAttribute("disabled");
+ },
+
+ disable: function(){
+ this.disabled = true;
+ this.textInputNode.setAttribute("disabled",true);
+ },
+
+ _getCaretPos: function(/*DomNode*/ element){
+ // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
+ if(dojo.lang.isNumber(element.selectionStart)){
+ // FIXME: this is totally borked on Moz < 1.3. Any recourse?
+ return element.selectionStart;
+ }else if(dojo.render.html.ie){
+ // in the case of a mouse click in a popup being handled,
+ // then the document.selection is not the textarea, but the popup
+ // var r = document.selection.createRange();
+ // hack to get IE 6 to play nice. What a POS browser.
+ var tr = document.selection.createRange().duplicate();
+ var ntr = element.createTextRange();
+ tr.move("character",0);
+ ntr.move("character",0);
+ try {
+ // If control doesnt have focus, you get an exception.
+ // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
+ // There appears to be no workaround for this - googled for quite a while.
+ ntr.setEndPoint("EndToEnd", tr);
+ return String(ntr.text).replace(/\r/g,"").length;
+ } catch (e){
+ return 0; // If focus has shifted, 0 is fine for caret pos.
+ }
+
+ }
+ },
+
+ _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
+ location = parseInt(location);
+ this._setSelectedRange(element, location, location);
+ },
+
+ _setSelectedRange: function(/*DomNode*/ element, /*Number*/ start, /*Number*/ end){
+ if(!end){ end = element.value.length; } // NOTE: Strange - should be able to put caret at start of text?
+ // Mozilla
+ // parts borrowed from http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130
+ if(element.setSelectionRange){
+ element.focus();
+ element.setSelectionRange(start, end);
+ }else if(element.createTextRange){ // IE
+ var range = element.createTextRange();
+ with(range){
+ collapse(true);
+ moveEnd('character', end);
+ moveStart('character', start);
+ select();
+ }
+ }else{ //otherwise try the event-creation hack (our own invention)
+ // do we need these?
+ element.value = element.value;
+ element.blur();
+ element.focus();
+ // figure out how far back to go
+ var dist = parseInt(element.value.length)-end;
+ var tchar = String.fromCharCode(37);
+ var tcc = tchar.charCodeAt(0);
+ for(var x = 0; x < dist; x++){
+ var te = document.createEvent("KeyEvents");
+ te.initKeyEvent("keypress", true, true, null, false, false, false, false, tcc, tcc);
+ element.dispatchEvent(te);
+ }
+ }
+ },
+
+ _handleKeyEvents: function(/*Event*/ evt){
+ // summary: handles keyboard events
+ if(evt.ctrlKey || evt.altKey || !evt.key){ return; }
+
+ // reset these
+ this._prev_key_backspace = false;
+ this._prev_key_esc = false;
+
+ var k = dojo.event.browser.keys;
+ var doSearch = true;
+
+ switch(evt.key){
+ case k.KEY_DOWN_ARROW:
+ if(!this.popupWidget.isShowingNow){
+ this._startSearchFromInput();
+ }
+ this._highlightNextOption();
+ dojo.event.browser.stopEvent(evt);
+ return;
+ case k.KEY_UP_ARROW:
+ this._highlightPrevOption();
+ dojo.event.browser.stopEvent(evt);
+ return;
+ case k.KEY_TAB:
+ // using linux alike tab for autocomplete
+ if(!this.autoComplete && this.popupWidget.isShowingNow && this._highlighted_option){
+ dojo.event.browser.stopEvent(evt);
+ this._selectOption({ 'target': this._highlighted_option, 'noHide': false});
+
+ // put caret last
+ this._setSelectedRange(this.textInputNode, this.textInputNode.value.length, null);
+ }else{
+ this._selectOption();
+ return;
+ }
+ break;
+ case k.KEY_ENTER:
+ // prevent submitting form if we press enter with list open
+ if(this.popupWidget.isShowingNow){
+ dojo.event.browser.stopEvent(evt);
+ }
+ if(this.autoComplete){
+ this._selectOption();
+ return;
+ }
+ // fallthrough
+ case " ":
+ if(this.popupWidget.isShowingNow && this._highlighted_option){
+ dojo.event.browser.stopEvent(evt);
+ this._selectOption();
+ this._hideResultList();
+ return;
+ }
+ break;
+ case k.KEY_ESCAPE:
+ this._hideResultList();
+ this._prev_key_esc = true;
+ return;
+ case k.KEY_BACKSPACE:
+ this._prev_key_backspace = true;
+ if(!this.textInputNode.value.length){
+ this.setAllValues("", "");
+ this._hideResultList();
+ doSearch = false;
+ }
+ break;
+ case k.KEY_RIGHT_ARROW: // fall through
+ case k.KEY_LEFT_ARROW: // fall through
+ doSearch = false;
+ break;
+ default:// non char keys (F1-F12 etc..) shouldn't open list
+ if(evt.charCode==0){
+ doSearch = false;
+ }
+ }
+
+ if(this.searchTimer){
+ clearTimeout(this.searchTimer);
+ }
+ if(doSearch){
+ // if we have gotten this far we dont want to keep our highlight
+ this._blurOptionNode();
+
+ // need to wait a tad before start search so that the event bubbles through DOM and we have value visible
+ this.searchTimer = setTimeout(dojo.lang.hitch(this, this._startSearchFromInput), this.searchDelay);
+ }
+ },
+
+ compositionEnd: function(/*Event*/ evt){
+ // summary: When inputting characters using an input method, such as Asian
+ // languages, it will generate this event instead of onKeyDown event
+ evt.key = evt.keyCode;
+ this._handleKeyEvents(evt);
+ },
+
+ onKeyUp: function(/*Event*/ evt){
+ // summary: callback on key up event
+ this.setValue(this.textInputNode.value);
+ },
+
+ setSelectedValue: function(/*String*/ value){
+ // summary:
+ // This sets a hidden value associated w/the displayed value.
+ // The hidden value (and this function) shouldn't be used; if
+ // you need a hidden value then use Select widget instead of ComboBox.
+ // TODO: remove?
+ // FIXME, not sure what to do here!
+ this.comboBoxSelectionValue.value = value;
+ },
+
+ setAllValues: function(/*String*/ value1, /*String*/ value2){
+ // summary:
+ // This sets the displayed value and hidden value.
+ // The hidden value (and this function) shouldn't be used; if
+ // you need a hidden value then use Select widget instead of ComboBox.
+ this.setSelectedValue(value2);
+ this.setValue(value1);
+ },
+
+ _focusOptionNode: function(/*DomNode*/ node){
+ // summary: does the actual highlight
+ if(this._highlighted_option != node){
+ this._blurOptionNode();
+ this._highlighted_option = node;
+ dojo.html.addClass(this._highlighted_option, "dojoComboBoxItemHighlight");
+ }
+ },
+
+ _blurOptionNode: function(){
+ // sumary: removes highlight on highlighted
+ if(this._highlighted_option){
+ dojo.html.removeClass(this._highlighted_option, "dojoComboBoxItemHighlight");
+ this._highlighted_option = null;
+ }
+ },
+
+ _highlightNextOption: function(){
+ if((!this._highlighted_option) || !this._highlighted_option.parentNode){
+ this._focusOptionNode(this.optionsListNode.firstChild);
+ }else if(this._highlighted_option.nextSibling){
+ this._focusOptionNode(this._highlighted_option.nextSibling);
+ }
+ dojo.html.scrollIntoView(this._highlighted_option);
+ },
+
+ _highlightPrevOption: function(){
+ if(this._highlighted_option && this._highlighted_option.previousSibling){
+ this._focusOptionNode(this._highlighted_option.previousSibling);
+ }else{
+ this._highlighted_option = null;
+ this._hideResultList();
+ return;
+ }
+ dojo.html.scrollIntoView(this._highlighted_option);
+ },
+
+ _itemMouseOver: function(/*Event*/ evt){
+ if (evt.target === this.optionsListNode){ return; }
+ this._focusOptionNode(evt.target);
+ dojo.html.addClass(this._highlighted_option, "dojoComboBoxItemHighlight");
+ },
+
+ _itemMouseOut: function(/*Event*/ evt){
+ if (evt.target === this.optionsListNode){ return; }
+ this._blurOptionNode();
+ },
+
+ onResize: function(){
+ // summary: this function is called when the input area has changed size
+ var inputSize = dojo.html.getContentBox(this.textInputNode);
+ if( inputSize.height <= 0 ){
+ // need more time to calculate size
+ dojo.lang.setTimeout(this, "onResize", 100);
+ return;
+ }
+ var buttonSize = { width: inputSize.height, height: inputSize.height};
+ dojo.html.setContentBox(this.downArrowNode, buttonSize);
+ },
+
+ fillInTemplate: function(/*Object*/ args, /*Object*/ frag){
+ // there's some browser specific CSS in ComboBox.css
+ dojo.html.applyBrowserClass(this.domNode);
+
+ var source = this.getFragNodeRef(frag);
+ if (! this.name && source.name){ this.name = source.name; }
+ this.comboBoxValue.name = this.name;
+ this.comboBoxSelectionValue.name = this.name+"_selected";
+
+ /* different nodes get different parts of the style */
+ dojo.html.copyStyle(this.domNode, source);
+ dojo.html.copyStyle(this.textInputNode, source);
+ dojo.html.copyStyle(this.downArrowNode, source);
+ with (this.downArrowNode.style){ // calculate these later
+ width = "0px";
+ height = "0px";
+ }
+
+ // Use specified data provider class; if no class is specified
+ // then use comboboxDataProvider or incrmentalComboBoxDataProvider
+ // depending on setting of mode
+ var dpClass;
+ if(this.dataProviderClass){
+ if(typeof this.dataProviderClass == "string"){
+ dpClass = dojo.evalObjPath(this.dataProviderClass)
+ }else{
+ dpClass = this.dataProviderClass;
+ }
+ }else{
+ if(this.mode == "remote"){
+ dpClass = dojo.widget.incrementalComboBoxDataProvider;
+ }else{
+ dpClass = dojo.widget.basicComboBoxDataProvider;
+ }
+ }
+ this.dataProvider = new dpClass(this, this.getFragNodeRef(frag));
+
+ this.popupWidget = new dojo.widget.createWidget("PopupContainer",
+ {toggle: this.dropdownToggle, toggleDuration: this.toggleDuration});
+ dojo.event.connect(this, 'destroy', this.popupWidget, 'destroy');
+ this.optionsListNode = this.popupWidget.domNode;
+ this.domNode.appendChild(this.optionsListNode);
+ dojo.html.addClass(this.optionsListNode, 'dojoComboBoxOptions');
+ dojo.event.connect(this.optionsListNode, 'onclick', this, '_selectOption');
+ dojo.event.connect(this.optionsListNode, 'onmouseover', this, '_onMouseOver');
+ dojo.event.connect(this.optionsListNode, 'onmouseout', this, '_onMouseOut');
+
+ // TODO: why does onmouseover and onmouseout connect to two separate handlers???
+ dojo.event.connect(this.optionsListNode, "onmouseover", this, "_itemMouseOver");
+ dojo.event.connect(this.optionsListNode, "onmouseout", this, "_itemMouseOut");
+ },
+
+ _openResultList: function(/*Array*/ results){
+ if (this.disabled){
+ return;
+ }
+ this._clearResultList();
+ if(!results.length){
+ this._hideResultList();
+ }
+
+ if( (this.autoComplete)&&
+ (results.length)&&
+ (!this._prev_key_backspace)&&
+ (this.textInputNode.value.length > 0)){
+ var cpos = this._getCaretPos(this.textInputNode);
+ // only try to extend if we added the last character at the end of the input
+ if((cpos+1) > this.textInputNode.value.length){
+ // only add to input node as we would overwrite Capitalisation of chars
+ this.textInputNode.value += results[0][0].substr(cpos);
+ // build a new range that has the distance from the earlier
+ // caret position to the end of the first string selected
+ this._setSelectedRange(this.textInputNode, cpos, this.textInputNode.value.length);
+ }
+ }
+
+ var even = true;
+ while(results.length){
+ var tr = results.shift();
+ if(tr){
+ var td = document.createElement("div");
+ td.appendChild(document.createTextNode(tr[0]));
+ td.setAttribute("resultName", tr[0]);
+ td.setAttribute("resultValue", tr[1]);
+ td.className = "dojoComboBoxItem "+((even) ? "dojoComboBoxItemEven" : "dojoComboBoxItemOdd");
+ even = (!even);
+ this.optionsListNode.appendChild(td);
+ }
+ }
+
+ // show our list (only if we have content, else nothing)
+ this._showResultList();
+ },
+
+ _onFocusInput: function(){
+ this._hasFocus = true;
+ },
+
+ _onBlurInput: function(){
+ this._hasFocus = false;
+ this._handleBlurTimer(true, 500);
+ },
+
+ _handleBlurTimer: function(/*Boolean*/clear, /*Number*/ millisec){
+ // summary: collect all blur timers issues here
+ if(this.blurTimer && (clear || millisec)){
+ clearTimeout(this.blurTimer);
+ }
+ if(millisec){ // we ignore that zero is false and never sets as that never happens in this widget
+ this.blurTimer = dojo.lang.setTimeout(this, "_checkBlurred", millisec);
+ }
+ },
+
+ _onMouseOver: function(/*Event*/ evt){
+ // summary: needed in IE and Safari as inputTextNode loses focus when scrolling optionslist
+ if(!this._mouseover_list){
+ this._handleBlurTimer(true, 0);
+ this._mouseover_list = true;
+ }
+ },
+
+ _onMouseOut:function(/*Event*/ evt){
+ // summary: needed in IE and Safari as inputTextNode loses focus when scrolling optionslist
+ var relTarget = evt.relatedTarget;
+ try { // fixes #1807
+ if(!relTarget || relTarget.parentNode != this.optionsListNode){
+ this._mouseover_list = false;
+ this._handleBlurTimer(true, 100);
+ this._tryFocus();
+ }
+ }catch(e){}
+ },
+
+ _isInputEqualToResult: function(/*String*/ result){
+ var input = this.textInputNode.value;
+ if(!this.dataProvider.caseSensitive){
+ input = input.toLowerCase();
+ result = result.toLowerCase();
+ }
+ return (input == result);
+ },
+
+ _isValidOption: function(){
+ var tgt = dojo.html.firstElement(this.optionsListNode);
+ var isValidOption = false;
+ while(!isValidOption && tgt){
+ if(this._isInputEqualToResult(tgt.getAttribute("resultName"))){
+ isValidOption = true;
+ }else{
+ tgt = dojo.html.nextElement(tgt);
+ }
+ }
+ return isValidOption;
+ },
+
+ _checkBlurred: function(){
+ if(!this._hasFocus && !this._mouseover_list){
+ this._hideResultList();
+ // clear the list if the user empties field and moves away.
+ if(!this.textInputNode.value.length){
+ this.setAllValues("", "");
+ return;
+ }
+
+ var isValidOption = this._isValidOption();
+ // enforce selection from option list
+ if(this.forceValidOption && !isValidOption){
+ this.setAllValues("", "");
+ return;
+ }
+ if(!isValidOption){// clear
+ this.setSelectedValue("");
+ }
+ }
+ },
+
+ _selectOption: function(/*Event*/ evt){
+ var tgt = null;
+ if(!evt){
+ evt = { target: this._highlighted_option };
+ }
+
+ if(!dojo.html.isDescendantOf(evt.target, this.optionsListNode)){
+ // handle autocompletion where the the user has hit ENTER or TAB
+
+ // if the input is empty do nothing
+ if(!this.textInputNode.value.length){
+ return;
+ }
+ tgt = dojo.html.firstElement(this.optionsListNode);
+
+ // user has input value not in option list
+ if(!tgt || !this._isInputEqualToResult(tgt.getAttribute("resultName"))){
+ return;
+ }
+ // otherwise the user has accepted the autocompleted value
+ }else{
+ tgt = evt.target;
+ }
+
+ while((tgt.nodeType!=1)||(!tgt.getAttribute("resultName"))){
+ tgt = tgt.parentNode;
+ if(tgt === dojo.body()){
+ return false;
+ }
+ }
+
+ this.selectedResult = [tgt.getAttribute("resultName"), tgt.getAttribute("resultValue")];
+ this.setAllValues(tgt.getAttribute("resultName"), tgt.getAttribute("resultValue"));
+ if(!evt.noHide){
+ this._hideResultList();
+ this._setSelectedRange(this.textInputNode, 0, null);
+ }
+ this._tryFocus();
+ },
+
+ _clearResultList: function(){
+ if(this.optionsListNode.innerHTML){
+ this.optionsListNode.innerHTML = ""; // browser natively knows how to collect this memory
+ }
+ },
+
+ _hideResultList: function(){
+ this.popupWidget.close();
+ },
+
+ _showResultList: function(){
+ // Our dear friend IE doesnt take max-height so we need to calculate that on our own every time
+ var childs = this.optionsListNode.childNodes;
+ if(childs.length){
+ var visibleCount = Math.min(childs.length,this.maxListLength);
+
+ with(this.optionsListNode.style)
+ {
+ display = "";
+ if(visibleCount == childs.length){
+ //no scrollbar is required, so unset height to let browser calcuate it,
+ //as in css, overflow is already set to auto
+ height = "";
+ }else{
+ //show it first to get the correct dojo.style.getOuterHeight(childs[0])
+ //FIXME: shall we cache the height of the item?
+ height = visibleCount * dojo.html.getMarginBox(childs[0]).height +"px";
+ }
+ width = (dojo.html.getMarginBox(this.domNode).width-2)+"px";
+ }
+ this.popupWidget.open(this.domNode, this, this.downArrowNode);
+ }else{
+ this._hideResultList();
+ }
+ },
+
+ handleArrowClick: function(){
+ // summary: callback when arrow is clicked
+ this._handleBlurTimer(true, 0);
+ this._tryFocus();
+ if(this.popupWidget.isShowingNow){
+ this._hideResultList();
+ }else{
+ // forces full population of results, if they click
+ // on the arrow it means they want to see more options
+ this._startSearch("");
+ }
+ },
+
+ _tryFocus: function(){
+ try {
+ this.textInputNode.focus();
+ } catch (e){
+ // element isn't focusable if disabled, or not visible etc - not easy to test for.
+ };
+ },
+
+ _startSearchFromInput: function(){
+ this._startSearch(this.textInputNode.value);
+ },
+
+ _startSearch: function(/*String*/ key){
+ this.dataProvider.startSearch(key, dojo.lang.hitch(this, "_openResultList"));
+ },
+
+ postCreate: function(){
+ this.onResize();
+
+ // TODO: add these attach events to template
+ dojo.event.connect(this.textInputNode, "onblur", this, "_onBlurInput");
+ dojo.event.connect(this.textInputNode, "onfocus", this, "_onFocusInput");
+
+ if (this.disabled){
+ this.disable();
+ }
+ var s = dojo.widget.html.stabile.getState(this.widgetId);
+ if (s){
+ this.setState(s);
+ }
+ }
+ }
+);
+
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+dojo.provide("struts.widget.ComboBox");
+
+
+
+
+struts.widget.ComboBoxDataProvider = function(combobox, node){
+ this.data = [];
+ this.searchLimit = combobox.searchLimit;
+ this.searchType = "STARTSTRING"; // may also be "STARTWORD" or "SUBSTRING"
+ this.caseSensitive = false;
+ // for caching optimizations
+ this._lastSearch = "";
+ this._lastSearchResults = null;
+
+ this.firstRequest = true;
+
+ this.cbox = combobox;
+ this.formId = this.cbox.formId;
+ this.formFilter = this.cbox.formFilter;
+ this.transport = this.cbox.transport;
+
+ this.getData = function(/*String*/ url){
+ //show indicator
+ dojo.html.show(this.cbox.indicator);
+
+ dojo.io.bind({
+ url: url,
+ formNode: dojo.byId(this.formId),
+ formFilter: window[this.formFilter],
+ transport: this.transport,
+ handler: dojo.lang.hitch(this, function(type, data, evt) {
+ //hide indicator
+ dojo.html.hide(this.cbox.indicator);
+
+ //if notifyTopics is published on the first request (onload)
+ //the value of listeners will be reset
+ if(!this.firstRequest || type == "error") {
+ this.cbox.notify.apply(this.cbox, [data, type, evt]);
+ }
+
+ this.firstRequest = false;
+ var arrData = null;
+ var dataByName = data[dojo.string.isBlank(this.cbox.dataFieldName) ? this.cbox.name : this.cbox.dataFieldName];
+ if(!dojo.lang.isArray(data)) {
+ //if there is a dataFieldName, take it
+ if(dataByName) {
+ if(dojo.lang.isArray(dataByName)) {
+ //ok, it is an array
+ arrData = dataByName;
+ } else if(dojo.lang.isObject(dataByName)) {
+ //it is an object, treat it like a map
+ arrData = [];
+ for(var key in dataByName){
+ arrData.push([key, dataByName[key]]);
+ }
+ }
+ } else {
+ //try to find a match
+ var tmpArrData = [];
+ for(var key in data){
+ //does it start with the field name? take it
+ if(dojo.string.startsWith(key, this.cbox.name)) {
+ arrData = data[key];
+ break;
+ } else {
+ //if nathing else is found, we will use values in this
+ //object as the data
+ tmpArrData.push([key, data[key]]);
+ }
+ //grab the first array found, we will use it if nothing else
+ //is found
+ if(!arrData && dojo.lang.isArray(data[key]) && !dojo.lang.isString(data[key])) {
+ arrData = data[key];
+ }
+ }
+ if(!arrData) {
+ arrData = tmpArrData;
+ }
+ }
+
+ data = arrData;
+ }
+ this.setData(data);
+ }),
+ mimetype: "text/json"
+ });
+ };
+
+ this.startSearch = function (searchStr, callback) {
+ // FIXME: need to add timeout handling here!!
+ this._preformSearch(searchStr, callback);
+ };
+
+ this._preformSearch = function(/*String*/ searchStr, callback){
+ //
+ // NOTE: this search is LINEAR, which means that it exhibits perhaps
+ // the worst possible speed characteristics of any search type. It's
+ // written this way to outline the responsibilities and interfaces for
+ // a search.
+ //
+ var st = this.searchType;
+ // FIXME: this is just an example search, which means that we implement
+ // only a linear search without any of the attendant (useful!) optimizations
+ var ret = [];
+ if(!this.caseSensitive){
+ searchStr = searchStr.toLowerCase();
+ }
+ for(var x=0; x<this.data.length; x++){
+ if(!this.data[x] || !this.data[x][0]) {
+ //needed for IE
+ continue;
+ }
+ if((this.searchLimit > 0) && (ret.length >= this.searchLimit)) {
+ break;
+ }
+ // FIXME: we should avoid copies if possible!
+ var dataLabel = new String((!this.caseSensitive) ? this.data[x][0].toLowerCase() : this.data[x][0]);
+ if(dataLabel.length < searchStr.length){
+ // this won't ever be a good search, will it? What if we start
+ // to support regex search?
+ continue;
+ }
+
+ if(st == "STARTSTRING"){
+ if(searchStr == dataLabel.substr(0, searchStr.length)){
+ ret.push(this.data[x]);
+ }
+ }else if(st == "SUBSTRING"){
+ // this one is a gimmie
+ if(dataLabel.indexOf(searchStr) >= 0){
+ ret.push(this.data[x]);
+ }
+ }else if(st == "STARTWORD"){
+ // do a substring search and then attempt to determine if the
+ // preceeding char was the beginning of the string or a
+ // whitespace char.
+ var idx = dataLabel.indexOf(searchStr);
+ if(idx == 0){
+ // implicit match
+ ret.push(this.data[x]);
+ }
+ if(idx <= 0){
+ // if we didn't match or implicily matched, march onward
+ continue;
+ }
+ // otherwise, we have to go figure out if the match was at the
+ // start of a word...
+ // this code is taken almost directy from nWidgets
+ var matches = false;
+ while(idx!=-1){
+ // make sure the match either starts whole string, or
+ // follows a space, or follows some punctuation
+ if(" ,/(".indexOf(dataLabel.charAt(idx-1)) != -1){
+ // FIXME: what about tab chars?
+ matches = true; break;
+ }
+ idx = dataLabel.indexOf(searchStr, idx+1);
+ }
+ if(!matches){
+ continue;
+ }else{
+ ret.push(this.data[x]);
+ }
+ }
+ }
+ callback(ret);
+ };
+
+ this.addData = function(/*Array*/ pairs){
+ // FIXME: incredibly naive and slow!
+ this.data = this.data.concat(pairs);
+ };
+
+ this.setData = function(/*Array*/ pdata){
+ // populate this.data and initialize lookup structures
+ this.data = pdata;
+ //all ellements must be a key and value pair
+ for(var i = 0; i < this.data.length; i++) {
+ var element = this.data[i];
+ if(!dojo.lang.isArray(element)) {
+ this.data[i] = [element, element];
+ }
+ }
+ };
+
+
+ if(!dojo.string.isBlank(this.cbox.dataUrl) && this.cbox.preload){
+ this.getData(this.cbox.dataUrl);
+ } else {
+ // check to see if we can populate the list from <option> elements
+ if((node)&&(node.nodeName.toLowerCase() == "select")){
+ // NOTE: we're not handling <optgroup> here yet
+ var opts = node.getElementsByTagName("option");
+ var ol = opts.length;
+ var data = [];
+ for(var x=0; x<ol; x++){
+ var text = opts[x].textContent || opts[x].innerText || opts[x].innerHTML;
+ var keyValArr = [String(text), String(opts[x].value)];
+ data.push(keyValArr);
+ if(opts[x].selected){
+ this.cbox.setAllValues(keyValArr[0], keyValArr[1]);
+ }
+ }
+ this.setData(data);
+ }
+ }
+};
+
+dojo.widget.defineWidget(
+ "struts.widget.ComboBox",
+ dojo.widget.ComboBox, {
+ widgetType : "ComboBox",
+
+ dropdownHeight: 120,
+ dropdownWidth: 0,
+ itemHeight: 0,
+
+ listenTopics : "",
+ notifyTopics : "",
+ notifyTopicsArray : null,
+ beforeNotifyTopics : "",
+ beforeNotifyTopicsArray : null,
+ afterNotifyTopics : "",
+ afterNotifyTopicsArray : null,
+ errorNotifyTopics : "",
+ errorNotifyTopicsArray : null,
+ valueNotifyTopics : "",
+ valueNotifyTopicsArray : null,
+ indicator : "",
+
+ formId : "",
+ formFilter : "",
+ dataProviderClass: "struts.widget.ComboBoxDataProvider",
+
+ loadOnType : false,
+ loadMinimum : 3,
+
+ initialValue : "",
+ initialKey : "",
+
+ visibleDownArrow : true,
+ fadeTime : 100,
+
+ //dojo has "stringstart" which is invalid
+ searchType: "STARTSTRING",
+
+ dataFieldName : "",
+ keyName: "",
+ //embedded the style in the template string in 0.4.2 release, not good
+ templateCssString: null,
+ templateCssString:"/*\r\n * $Id$\r\n *\r\n * Licensed to the Apache Software Foundation (ASF) under one\r\n * or more contributor license agreements. See the NOTICE file\r\n * distributed with this work for additional information\r\n * regarding copyright ownership. The ASF licenses this file\r\n * to you under the Apache License, Version 2.0 (the\r\n * \"License\"); you may not use this file except in compliance\r\n * with the License. You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing,\r\n * software distributed under the License is distributed on an\r\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n * KIND, either express or implied. See the License for the\r\n * specific language governing permissions and limitations\r\n * under the License.\r\n */\r\n\r\n.dojoComboBoxOuter {\r\n\tborder: 0px !important;\r\n\tmargin: 0px !important;\r\n
\tpadding: 0px !important;\r\n\tbackground: transparent !important;\r\n\twhite-space: nowrap !important;\r\n}\r\n\r\n.dojoComboBox {\r\n\tborder: 1px inset #afafaf;\r\n\tmargin: 0px;\r\n\tpadding: 0px;\r\n\tvertical-align: middle !important;\r\n\tfloat: none !important;\r\n\tposition: static !important;\r\n\tdisplay: inline;\r\n}\r\n\r\n/* the input box */\r\ninput.dojoComboBox {\r\n\tborder-right-width: 1px !important;\r\n\tmargin-right: 0px !important;\r\n\tpadding-right: 0px !important;\r\n}\r\n\r\n/* the down arrow */\r\nimg.dojoComboBox {\r\n\tborder-left-width: 0px !important;\r\n\tpadding-left: 0px !important;\r\n\tmargin-left: 0px !important;\r\n}\r\n\r\n/* IE vertical-alignment calculations can be off by +-1 but these margins are collapsed away */\r\n.dj_ie img.dojoComboBox {\r\n\tmargin-top: 1px;\r\n\tmargin-bottom: 1px;\r\n}\r\n\r\n/* the drop down */\r\n.dojoComboBoxOptions {\r\n\tfont-family: Verdana, Helvetica, Garamond, sans-serif;\r\n\t/* font-size: 0.7em; */
\r\n\tbackground-color: white;\r\n\tborder: 1px solid #afafaf;\r\n\tposition: absolute;\r\n\tz-index: 1000;\r\n\toverflow: auto;\r\n\tcursor: default;\r\n}\r\n\r\n.dojoComboBoxItem {\r\n\tpadding-left: 2px;\r\n\tpadding-top: 2px;\r\n\tmargin: 0px;\r\n}\r\n\r\n.dojoComboBoxItemEven {\r\n\tbackground-color: #f4f4f4;\r\n}\r\n\r\n.dojoComboBoxItemOdd {\r\n\tbackground-color: white;\r\n}\r\n\r\n.dojoComboBoxItemHighlight {\r\n\tbackground-color: #63709A;\r\n\tcolor: white;\r\n}\r\n",templateCssPath: dojo.uri.dojoUri("struts/ComboBox.css"),
+
+ //how many results are shown
+ searchLimit : 30,
+
+ transport : "",
+
+ //load options when page loads
+ preload : true,
+
+ tabIndex: "",
+
+ //from Dojo's ComboBox
+ showResultList: function() {
+ // Our dear friend IE doesnt take max-height so we need to calculate that on our own every time
+ var childs = this.optionsListNode.childNodes;
+ if(childs.length){
+
+ this.optionsListNode.style.width = this.dropdownWidth === 0 ? (dojo.html.getMarginBox(this.domNode).width-2)+"px" : this.dropdownWidth + "px";
+
+ if(this.itemHeight === 0 || dojo.string.isBlank(this.textInputNode.value)) {
+ this.optionsListNode.style.height = this.dropdownHeight + "px";
+ this.optionsListNode.style.display = "";
+ this.itemHeight = dojo.html.getMarginBox(childs[0]).height;
+ }
+
+ //if there is extra space, adjust height
+ var totalHeight = this.itemHeight * childs.length;
+ if(totalHeight < this.dropdownHeight) {
+ this.optionsListNode.style.height = totalHeight + 2 + "px";
+ } else {
+ this.optionsListNode.style.height = this.dropdownHeight + "px";
+ }
+
+ this.popupWidget.open(this.domNode, this, this.downArrowNode);
+ } else {
+ this._hideResultList();
+ }
+ },
+
+ _openResultList: function(/*Array*/ results){
+ if (this.disabled) {
+ return;
+ }
+ this._clearResultList();
+ if(!results.length){
+ this._hideResultList();
+ }
+
+ if( (this.autoComplete)&&
+ (results.length)&&
+ (!this._prev_key_backspace)&&
+ (this.textInputNode.value.length > 0)){
+ var cpos = this._getCaretPos(this.textInputNode);
+ // only try to extend if we added the last character at the end of the input
+ if((cpos+1) > this.textInputNode.value.length){
+ // only add to input node as we would overwrite Capitalisation of chars
+ this.textInputNode.value += results[0][0].substr(cpos);
+ // build a new range that has the distance from the earlier
+ // caret position to the end of the first string selected
+ this._setSelectedRange(this.textInputNode, cpos, this.textInputNode.value.length);
+ }
+ }
+ var typedText = this.textInputNode.value;
+ var even = true;
+ while(results.length){
+ var tr = results.shift();
+ if(tr){
+ var td = document.createElement("div");
+ var text = tr[0];
+ var i = text.toLowerCase().indexOf(typedText.toLowerCase());
+ if(i >= 0) {
+ var pre = text.substring(0, i);
+ var matched = text.substring(i, i + typedText.length);
+ var post = text.substring(i + typedText.length);
+
+ if(!dojo.string.isBlank(pre)) {
+ td.appendChild(document.createTextNode(pre));
+ }
+ var boldNode = document.createElement("b");
+ td.appendChild(boldNode);
+ boldNode.appendChild(document.createTextNode(matched));
+ td.appendChild(document.createTextNode(post));
+ } else {
+ td.appendChild(document.createTextNode(tr[0]));
+ }
+
+ td.setAttribute("resultName", tr[0]);
+ td.setAttribute("resultValue", tr[1]);
+ td.className = "dojoComboBoxItem "+((even) ? "dojoComboBoxItemEven" : "dojoComboBoxItemOdd");
+ even = (!even);
+ this.optionsListNode.appendChild(td);
+ }
+ }
+
+ // show our list (only if we have content, else nothing)
+ this.showResultList();
+ },
+
+ postCreate : function() {
+ struts.widget.ComboBox.superclass.postCreate.apply(this);
+ var self = this;
+ //events
+ if(!dojo.string.isBlank(this.listenTopics)) {
+ var topics = this.listenTopics.split(",");
+ for(var i = 0; i < topics.length; i++) {
+ dojo.event.topic.subscribe(topics[i], function() {
+ var request = {cancel: false};
+ self.notify(this.widgetId, "before", request);
+ if(request.cancel) {
+ return;
+ }
+ self.clearValues();
+ self.dataProvider.getData(self.dataUrl);
+ });
+ }
+ }
+
+ //notify topics
+ if(!dojo.string.isBlank(this.notifyTopics)) {
+ this.notifyTopicsArray = this.notifyTopics.split(",");
+ }
+
+ //before topics
+ if(!dojo.string.isBlank(this.beforeNotifyTopics)) {
+ this.beforeNotifyTopicsArray = this.beforeNotifyTopics.split(",");
+ }
+
+ //after topics
+ if(!dojo.string.isBlank(this.afterNotifyTopics)) {
+ this.afterNotifyTopicsArray = this.afterNotifyTopics.split(",");
+ }
+
+ //error topics
+ if(!dojo.string.isBlank(this.errorNotifyTopics)) {
+ this.errorNotifyTopicsArray = this.errorNotifyTopics.split(",");
+ }
+
+ //value topics
+ if(!dojo.string.isBlank(this.valueNotifyTopics)) {
+ this.valueNotifyTopicsArray = this.valueNotifyTopics.split(",");
+ }
+
+ //better name
+ this.comboBoxSelectionValue.name = dojo.string.isBlank(this.keyName) ? this.name + "Key" : this.keyName;
+
+ //init values
+ this.comboBoxValue.value = this.initialValue;
+ this.comboBoxSelectionValue.value = this.initialKey;
+ this.textInputNode.value = this.initialValue;
+
+ //tabindex
+ if(!dojo.string.isBlank(this.tabIndex)) {
+ this.textInputNode.tabIndex = this.tabIndex;
+ }
+
+ //hide arrow?
+ if(!this.visibleDownArrow) {
[... 6702 lines stripped ...]