From: Andrew Dolgov Date: Fri, 4 Mar 2011 16:02:28 +0000 (+0300) Subject: build custom layer of Dojo to speed up loading of tt-rss (refs #293) X-Git-Tag: 1.5.2~32 X-Git-Url: https://git.wh0rd.org/?a=commitdiff_plain;h=a089699c8915636ba4f158d77dba9b012bc93208;p=tt-rss.git build custom layer of Dojo to speed up loading of tt-rss (refs #293) --- diff --git a/lib/dojo-src/.gitignore b/lib/dojo-src/.gitignore new file mode 100644 index 00000000..54650a46 --- /dev/null +++ b/lib/dojo-src/.gitignore @@ -0,0 +1,5 @@ +dijit +dojo +dojox +release +util diff --git a/lib/dojo-src/profile.js b/lib/dojo-src/profile.js new file mode 100644 index 00000000..572b9423 --- /dev/null +++ b/lib/dojo-src/profile.js @@ -0,0 +1,41 @@ +dependencies = { + layers: [ + { + name: "tt-rss-layer.js", + resourceName: "tt-rss-layer", + dependencies: [ + "dojo.parser", + "dijit.dijit", + "dojo.NodeList-fx", + "dijit.ColorPalette", + "dijit.Dialog", + "dijit.form.Button", + "dijit.form.CheckBox", + "dijit.form.DropDownButton", + "dijit.form.FilteringSelect", + "dijit.form.Form", + "dijit.form.RadioButton", + "dijit.form.Select", + "dijit.form.SimpleTextarea", + "dijit.form.TextBox", + "dijit.form.ValidationTextBox", + "dijit.InlineEditBox", + "dijit.layout.AccordionContainer", + "dijit.layout.BorderContainer", + "dijit.layout.ContentPane", + "dijit.layout.TabContainer", + "dijit.Menu", + "dijit.ProgressBar", + "dijit.ProgressBar", + "dijit.Toolbar", + "dijit.Tree", + "dijit.tree.dndSource", + "dojo.data.ItemFileWriteStore", + "dojo.parser", + ] + } + ], + prefixes: [ + [ "dijit", "../dijit" ] + ] +} diff --git a/lib/dojo-src/rebuild-dojo.sh b/lib/dojo-src/rebuild-dojo.sh new file mode 100755 index 00000000..70862753 --- /dev/null +++ b/lib/dojo-src/rebuild-dojo.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# This script rebuilds customized layer of Dojo for tt-rss +# Place unpacked Dojo source release in this directory and run this script. +# It will automatically replace previous build of Dojo in ../dojo + +# Dojo requires Java runtime to build. Further information on rebuilding Dojo +# is available here: http://dojotoolkit.org/reference-guide/build/index.html + +if [ -d util/buildscripts/ ]; then + pushd util/buildscripts + ./build.sh profileFile=../../profile.js action=clean,release version=1.5.0 releaseName= + popd + + if [ -d release/dojo ]; then + rm -rf ../dojo + cp -r release/dojo .. + else + echo $0: ERROR: Dojo build seems to have failed. + fi +else + echo $0: ERROR: Please unpack Dojo source release into current directory. +fi diff --git a/lib/dojo/AdapterRegistry.js b/lib/dojo/AdapterRegistry.js index 1939cfa9..bb6a60f4 100644 --- a/lib/dojo/AdapterRegistry.js +++ b/lib/dojo/AdapterRegistry.js @@ -5,35 +5,102 @@ */ -if(!dojo._hasResource["dojo.AdapterRegistry"]){ -dojo._hasResource["dojo.AdapterRegistry"]=true; +if(!dojo._hasResource["dojo.AdapterRegistry"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.AdapterRegistry"] = true; dojo.provide("dojo.AdapterRegistry"); -dojo.AdapterRegistry=function(_1){ -this.pairs=[]; -this.returnWrappers=_1||false; -}; -dojo.extend(dojo.AdapterRegistry,{register:function(_2,_3,_4,_5,_6){ -this.pairs[((_6)?"unshift":"push")]([_2,_3,_4,_5]); -},match:function(){ -for(var i=0;i1
  • 2
  • 3
  • ", + // | { + // | parseContent: true, + // | onBegin: function(){ + // | this.content = this.content.replace(/([0-9])/g, this.id + ": $1"); + // | this.inherited("onBegin", arguments); + // | } + // | }).removeClass("notdone").addClass("done"); + + var dhs = new dojo.html._ContentSetter(params || {}); + this.forEach(function(elm){ + dhs.node = elm; + dhs.set(content); + dhs.tearDown(); + }); + return this; // dojo.NodeList + } }); -return this; -}}); + } diff --git a/lib/dojo/NodeList-manipulate.js b/lib/dojo/NodeList-manipulate.js index b0b7498a..171540fa 100644 --- a/lib/dojo/NodeList-manipulate.js +++ b/lib/dojo/NodeList-manipulate.js @@ -5,208 +5,727 @@ */ -if(!dojo._hasResource["dojo.NodeList-manipulate"]){ -dojo._hasResource["dojo.NodeList-manipulate"]=true; +if(!dojo._hasResource["dojo.NodeList-manipulate"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.NodeList-manipulate"] = true; dojo.provide("dojo.NodeList-manipulate"); -(function(){ -function _1(_2){ -var _3="",ch=_2.childNodes; -for(var i=0,n;n=ch[i];i++){ -if(n.nodeType!=8){ -if(n.nodeType==1){ -_3+=_1(n); -}else{ -_3+=n.nodeValue; -} -} -} -return _3; -}; -function _4(_5){ -while(_5.childNodes[0]&&_5.childNodes[0].nodeType==1){ -_5=_5.childNodes[0]; -} -return _5; -}; -function _6(_7,_8){ -if(typeof _7=="string"){ -_7=dojo._toDom(_7,(_8&&_8.ownerDocument)); -if(_7.nodeType==11){ -_7=_7.childNodes[0]; -} -}else{ -if(_7.nodeType==1&&_7.parentNode){ -_7=_7.cloneNode(false); -} -} -return _7; + +/*===== +dojo["NodeList-manipulate"] = { + // summary: Adds a chainable methods to dojo.query() / Nodelist instances for manipulating HTML + // and DOM nodes and their properties. }; -dojo.extend(dojo.NodeList,{_placeMultiple:function(_9,_a){ -var _b=typeof _9=="string"||_9.nodeType?dojo.query(_9):_9; -var _c=[]; -for(var i=0;i<_b.length;i++){ -var _d=_b[i]; -var _e=this.length; -for(var j=_e-1,_f;_f=this[j];j--){ -if(i>0){ -_f=this._cloneNode(_f); -_c.unshift(_f); -} -if(j==_e-1){ -dojo.place(_f,_d,_a); -}else{ -_d.parentNode.insertBefore(_f,_d); -} -_d=_f; -} -} -if(_c.length){ -_c.unshift(0); -_c.unshift(this.length-1); -Array.prototype.splice.apply(this,_c); -} -return this; -},innerHTML:function(_10){ -if(arguments.length){ -return this.addContent(_10,"only"); -}else{ -return this[0].innerHTML; -} -},text:function(_11){ -if(arguments.length){ -for(var i=0,_12;_12=this[i];i++){ -if(_12.nodeType==1){ -dojo.empty(_12); -_12.appendChild(_12.ownerDocument.createTextNode(_11)); -} -} -return this; -}else{ -var _13=""; -for(i=0;_12=this[i];i++){ -_13+=_1(_12); -} -return _13; -} -},val:function(_14){ -if(arguments.length){ -var _15=dojo.isArray(_14); -for(var _16=0,_17;_17=this[_16];_16++){ -var _18=_17.nodeName.toUpperCase(); -var _19=_17.type; -var _1a=_15?_14[_16]:_14; -if(_18=="SELECT"){ -var _1b=_17.options; -for(var i=0;i<_1b.length;i++){ -var opt=_1b[i]; -if(_17.multiple){ -opt.selected=(dojo.indexOf(_14,opt.value)!=-1); -}else{ -opt.selected=(opt.value==_1a); -} -} -}else{ -if(_19=="checkbox"||_19=="radio"){ -_17.checked=(_17.value==_1a); -}else{ -_17.value=_1a; -} -} -} -return this; -}else{ -_17=this[0]; -if(!_17||_17.nodeType!=1){ -return undefined; -} -_14=_17.value||""; -if(_17.nodeName.toUpperCase()=="SELECT"&&_17.multiple){ -_14=[]; -_1b=_17.options; -for(i=0;i<_1b.length;i++){ -opt=_1b[i]; -if(opt.selected){ -_14.push(opt.value); -} -} -if(!_14.length){ -_14=null; -} -} -return _14; -} -},append:function(_1c){ -return this.addContent(_1c,"last"); -},appendTo:function(_1d){ -return this._placeMultiple(_1d,"last"); -},prepend:function(_1e){ -return this.addContent(_1e,"first"); -},prependTo:function(_1f){ -return this._placeMultiple(_1f,"first"); -},after:function(_20){ -return this.addContent(_20,"after"); -},insertAfter:function(_21){ -return this._placeMultiple(_21,"after"); -},before:function(_22){ -return this.addContent(_22,"before"); -},insertBefore:function(_23){ -return this._placeMultiple(_23,"before"); -},remove:dojo.NodeList.prototype.orphan,wrap:function(_24){ -if(this[0]){ -_24=_6(_24,this[0]); -for(var i=0,_25;_25=this[i];i++){ -var _26=this._cloneNode(_24); -if(_25.parentNode){ -_25.parentNode.replaceChild(_26,_25); -} -var _27=_4(_26); -_27.appendChild(_25); -} -} -return this; -},wrapAll:function(_28){ -if(this[0]){ -_28=_6(_28,this[0]); -this[0].parentNode.replaceChild(_28,this[0]); -var _29=_4(_28); -for(var i=0,_2a;_2a=this[i];i++){ -_29.appendChild(_2a); -} -} -return this; -},wrapInner:function(_2b){ -if(this[0]){ -_2b=_6(_2b,this[0]); -for(var i=0;i0); -_2e.parentNode.removeChild(_2e); -} -return this; -},replaceAll:function(_2f){ -var nl=dojo.query(_2f); -var _30=this._normalize(this,this[0]); -for(var i=0,_31;_31=nl[i];i++){ -this._place(_30,_31,"before",i>0); -_31.parentNode.removeChild(_31); -} -return this; -},clone:function(){ -var ary=[]; -for(var i=0;i tags do not end up in + // the text as any sort of line return. + var text = "", ch = node.childNodes; + for(var i = 0, n; n = ch[i]; i++){ + //Skip comments. + if(n.nodeType != 8){ + if(n.nodeType == 1){ + text += getText(n); + }else{ + text += n.nodeValue; + } + } + } + return text; + } + + function getWrapInsertion(/*DOMNode*/node){ + // summary: + // finds the innermost element to use for wrap insertion. + + //Make it easy, assume single nesting, no siblings. + while(node.childNodes[0] && node.childNodes[0].nodeType == 1){ + node = node.childNodes[0]; + } + return node; //DOMNode + } + + function makeWrapNode(/*DOMNode||String*/html, /*DOMNode*/refNode){ + // summary: + // convert HTML into nodes if it is not already a node. + if(typeof html == "string"){ + html = dojo._toDom(html, (refNode && refNode.ownerDocument)); + if(html.nodeType == 11){ + //DocumentFragment cannot handle cloneNode, so choose first child. + html = html.childNodes[0]; + } + }else if(html.nodeType == 1 && html.parentNode){ + //This element is already in the DOM clone it, but not its children. + html = html.cloneNode(false); + } + return html; /*DOMNode*/ + } + + dojo.extend(dojo.NodeList, { + _placeMultiple: function(/*String||Node||NodeList*/query, /*String*/position){ + // summary: + // private method for inserting queried nodes into all nodes in this NodeList + // at different positions. Differs from NodeList.place because it will clone + // the nodes in this NodeList if the query matches more than one element. + var nl2 = typeof query == "string" || query.nodeType ? dojo.query(query) : query; + var toAdd = []; + for(var i = 0; i < nl2.length; i++){ + //Go backwards in DOM to make dom insertions easier via insertBefore + var refNode = nl2[i]; + var length = this.length; + for(var j = length - 1, item; item = this[j]; j--){ + if(i > 0){ + //Need to clone the item. This also means + //it needs to be added to the current NodeList + //so it can also be the target of other chaining operations. + item = this._cloneNode(item); + toAdd.unshift(item); + } + if(j == length - 1){ + dojo.place(item, refNode, position); + }else{ + refNode.parentNode.insertBefore(item, refNode); + } + refNode = item; + } + } + + if(toAdd.length){ + //Add the toAdd items to the current NodeList. Build up list of args + //to pass to splice. + toAdd.unshift(0); + toAdd.unshift(this.length - 1); + Array.prototype.splice.apply(this, toAdd); + } + + return this; //dojo.NodeList + }, + + innerHTML: function(/*String?||DOMNode?|NodeList?*/value){ + // summary: + // allows setting the innerHTML of each node in the NodeList, + // if there is a value passed in, otherwise, reads the innerHTML value of the first node. + // description: + // This method is simpler than the dojo.NodeList.html() method provided by + // `dojo.NodeList-html`. This method just does proper innerHTML insertion of HTML fragments, + // and it allows for the innerHTML to be read for the first node in the node list. + // Since dojo.NodeList-html already took the "html" name, this method is called + // "innerHTML". However, if dojo.NodeList-html has not been loaded yet, this + // module will define an "html" method that can be used instead. Be careful if you + // are working in an environment where it is possible that dojo.NodeList-html could + // have been loaded, since its definition of "html" will take precedence. + // The nodes represented by the value argument will be cloned if more than one + // node is in this NodeList. The nodes in this NodeList are returned in the "set" + // usage of this method, not the HTML that was inserted. + // returns: + // if no value is passed, the result is String, the innerHTML of the first node. + // If a value is passed, the return is this dojo.NodeList + // example: + // assume a DOM created by this markup: + // |
    + // |
    + // This code inserts

    Hello World

    into both divs: + // | dojo.query("div").innerHTML("

    Hello World

    "); + // example: + // assume a DOM created by this markup: + // |

    Hello Mars

    + // |

    Hello World

    + // This code returns "

    Hello Mars

    ": + // | var message = dojo.query("div").innerHTML(); + if(arguments.length){ + return this.addContent(value, "only"); //dojo.NodeList + }else{ + return this[0].innerHTML; //String + } + }, + + /*===== + html: function(value){ + // summary: + // see the information for "innerHTML". "html" is an alias for "innerHTML", but is + // only defined if dojo.NodeList-html has not been loaded. + // description: + // An alias for the "innerHTML" method, but only defined if there is not an existing + // "html" method on dojo.NodeList. Be careful if you are working in an environment + // where it is possible that dojo.NodeList-html could have been loaded, since its + // definition of "html" will take precedence. If you are not sure if dojo.NodeList-html + // could be loaded, use the "innerHTML" method. + // value: String?||DOMNode?||NodeList? + // optional. The HTML fragment to use as innerHTML. If value is not passed, then the innerHTML + // of the first element in this NodeList is returned. + // returns: + // if no value is passed, the result is String, the innerHTML of the first node. + // If a value is passed, the return is this dojo.NodeList + return; // dojo.NodeList + return; // String + }, + =====*/ + + text: function(/*String*/value){ + // summary: + // allows setting the text value of each node in the NodeList, + // if there is a value passed in, otherwise, returns the text value for all the + // nodes in the NodeList in one string. + // example: + // assume a DOM created by this markup: + // |
    + // |
    + // This code inserts "Hello World" into both divs: + // | dojo.query("div").text("Hello World"); + // example: + // assume a DOM created by this markup: + // |

    Hello Mars today

    + // |

    Hello World

    + // This code returns "Hello Mars today": + // | var message = dojo.query("div").text(); + // returns: + // if no value is passed, the result is String, the text value of the first node. + // If a value is passed, the return is this dojo.NodeList + if(arguments.length){ + for(var i = 0, node; node = this[i]; i++){ + if(node.nodeType == 1){ + dojo.empty(node); + node.appendChild(node.ownerDocument.createTextNode(value)); + } + } + return this; //dojo.NodeList + }else{ + var result = ""; + for(i = 0; node = this[i]; i++){ + result += getText(node); + } + return result; //String + } + }, + + val: function(/*String||Array*/value){ + // summary: + // If a value is passed, allows seting the value property of form elements in this + // NodeList, or properly selecting/checking the right value for radio/checkbox/select + // elements. If no value is passed, the value of the first node in this NodeList + // is returned. + // returns: + // if no value is passed, the result is String or an Array, for the value of the + // first node. + // If a value is passed, the return is this dojo.NodeList + // example: + // assume a DOM created by this markup: + // | + // | + // This code gets and sets the values for the form fields above: + // | dojo.query('[type="text"]').val(); //gets value foo + // | dojo.query('[type="text"]').val("bar"); //sets the input's value to "bar" + // | dojo.query("select").val() //gets array value ["red", "yellow"] + // | dojo.query("select").val(["blue", "yellow"]) //Sets the blue and yellow options to selected. + + //Special work for input elements. + if(arguments.length){ + var isArray = dojo.isArray(value); + for(var index = 0, node; node = this[index]; index++){ + var name = node.nodeName.toUpperCase(); + var type = node.type; + var newValue = isArray ? value[index] : value; + + if(name == "SELECT"){ + var opts = node.options; + for(var i = 0; i < opts.length; i++){ + var opt = opts[i]; + if(node.multiple){ + opt.selected = (dojo.indexOf(value, opt.value) != -1); + }else{ + opt.selected = (opt.value == newValue); + } + } + }else if(type == "checkbox" || type == "radio"){ + node.checked = (node.value == newValue); + }else{ + node.value = newValue; + } + } + return this; //dojo.NodeList + }else{ + //node already declared above. + node = this[0]; + if(!node || node.nodeType != 1){ + return undefined; + } + value = node.value || ""; + if(node.nodeName.toUpperCase() == "SELECT" && node.multiple){ + //A multivalued selectbox. Do the pain. + value = []; + //opts declared above in if block. + opts = node.options; + //i declared above in if block; + for(i = 0; i < opts.length; i++){ + //opt declared above in if block + opt = opts[i]; + if(opt.selected){ + value.push(opt.value); + } + } + if(!value.length){ + value = null; + } + } + return value; //String||Array + } + }, + + append: function(/*String||DOMNode||NodeList*/content){ + // summary: + // appends the content to every node in the NodeList. + // description: + // The content will be cloned if the length of NodeList + // is greater than 1. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // dojo.NodeList, the nodes currently in this NodeList will be returned, + // not the appended content. + // example: + // assume a DOM created by this markup: + // |

    Hello Mars

    + // |

    Hello World

    + // Running this code: + // | dojo.query("div").append("append"); + // Results in this DOM structure: + // |

    Hello Mars

    append
    + // |

    Hello World

    append
    + return this.addContent(content, "last"); //dojo.NodeList + }, + + appendTo: function(/*String*/query){ + // summary: + // appends nodes in this NodeList to the nodes matched by + // the query passed to appendTo. + // description: + // The nodes in this NodeList will be cloned if the query + // matches more than one element. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // dojo.NodeList, the nodes currently in this NodeList will be returned, + // not the matched nodes from the query. + // example: + // assume a DOM created by this markup: + // | append + // |

    Hello Mars

    + // |

    Hello World

    + // Running this code: + // | dojo.query("span").appendTo("p"); + // Results in this DOM structure: + // |

    Hello Marsappend

    + // |

    Hello Worldappend

    + return this._placeMultiple(query, "last"); //dojo.NodeList + }, + + prepend: function(/*String||DOMNode||NodeList*/content){ + // summary: + // prepends the content to every node in the NodeList. + // description: + // The content will be cloned if the length of NodeList + // is greater than 1. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // dojo.NodeList, the nodes currently in this NodeList will be returned, + // not the appended content. + // assume a DOM created by this markup: + // |

    Hello Mars

    + // |

    Hello World

    + // Running this code: + // | dojo.query("div").prepend("prepend"); + // Results in this DOM structure: + // |
    prepend

    Hello Mars

    + // |
    prepend

    Hello World

    + return this.addContent(content, "first"); //dojo.NodeList + }, + + prependTo: function(/*String*/query){ + // summary: + // prepends nodes in this NodeList to the nodes matched by + // the query passed to prependTo. + // description: + // The nodes in this NodeList will be cloned if the query + // matches more than one element. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // dojo.NodeList, the nodes currently in this NodeList will be returned, + // not the matched nodes from the query. + // example: + // assume a DOM created by this markup: + // | prepend + // |

    Hello Mars

    + // |

    Hello World

    + // Running this code: + // | dojo.query("span").prependTo("p"); + // Results in this DOM structure: + // |

    prependHello Mars

    + // |

    prependHello World

    + return this._placeMultiple(query, "first"); //dojo.NodeList + }, + + after: function(/*String||Element||NodeList*/content){ + // summary: + // Places the content after every node in the NodeList. + // description: + // The content will be cloned if the length of NodeList + // is greater than 1. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // dojo.NodeList, the nodes currently in this NodeList will be returned, + // not the appended content. + // example: + // assume a DOM created by this markup: + // |

    Hello Mars

    + // |

    Hello World

    + // Running this code: + // | dojo.query("div").after("after"); + // Results in this DOM structure: + // |

    Hello Mars

    after + // |

    Hello World

    after + return this.addContent(content, "after"); //dojo.NodeList + }, + + insertAfter: function(/*String*/query){ + // summary: + // The nodes in this NodeList will be placed after the nodes + // matched by the query passed to insertAfter. + // description: + // The nodes in this NodeList will be cloned if the query + // matches more than one element. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // dojo.NodeList, the nodes currently in this NodeList will be returned, + // not the matched nodes from the query. + // example: + // assume a DOM created by this markup: + // | after + // |

    Hello Mars

    + // |

    Hello World

    + // Running this code: + // | dojo.query("span").insertAfter("p"); + // Results in this DOM structure: + // |

    Hello Mars

    after + // |

    Hello World

    after + return this._placeMultiple(query, "after"); //dojo.NodeList + }, + + before: function(/*String||DOMNode||NodeList*/content){ + // summary: + // Places the content before every node in the NodeList. + // description: + // The content will be cloned if the length of NodeList + // is greater than 1. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // dojo.NodeList, the nodes currently in this NodeList will be returned, + // not the appended content. + // example: + // assume a DOM created by this markup: + // |

    Hello Mars

    + // |

    Hello World

    + // Running this code: + // | dojo.query("div").before("before"); + // Results in this DOM structure: + // | before

    Hello Mars

    + // | before

    Hello World

    + return this.addContent(content, "before"); //dojo.NodeList + }, + + insertBefore: function(/*String*/query){ + // summary: + // The nodes in this NodeList will be placed after the nodes + // matched by the query passed to insertAfter. + // description: + // The nodes in this NodeList will be cloned if the query + // matches more than one element. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // dojo.NodeList, the nodes currently in this NodeList will be returned, + // not the matched nodes from the query. + // example: + // assume a DOM created by this markup: + // | before + // |

    Hello Mars

    + // |

    Hello World

    + // Running this code: + // | dojo.query("span").insertBefore("p"); + // Results in this DOM structure: + // | before

    Hello Mars

    + // | before

    Hello World

    + return this._placeMultiple(query, "before"); //dojo.NodeList + }, + + /*===== + remove: function(simpleFilter){ + // summary: + // alias for dojo.NodeList's orphan method. Removes elements + // in this list that match the simple filter from their parents + // and returns them as a new NodeList. + // simpleFilter: String + // single-expression CSS rule. For example, ".thinger" or + // "#someId[attrName='value']" but not "div > span". In short, + // anything which does not invoke a descent to evaluate but + // can instead be used to test a single node is acceptable. + // returns: + // dojo.NodeList + return; // dojo.NodeList + }, + =====*/ + remove: dojo.NodeList.prototype.orphan, + + wrap: function(/*String||DOMNode*/html){ + // summary: + // Wrap each node in the NodeList with html passed to wrap. + // description: + // html will be cloned if the NodeList has more than one + // element. Only DOM nodes are cloned, not any attached + // event handlers. + // returns: + // dojo.NodeList, the nodes in the current NodeList will be returned, + // not the nodes from html argument. + // example: + // assume a DOM created by this markup: + // | one + // | two + // Running this code: + // | dojo.query("b").wrap("
    "); + // Results in this DOM structure: + // |
    one
    + // |
    two
    + if(this[0]){ + html = makeWrapNode(html, this[0]); + + //Now cycle through the elements and do the insertion. + for(var i = 0, node; node = this[i]; i++){ + //Always clone because if html is used to hold one of + //the "this" nodes, then on the clone of html it will contain + //that "this" node, and that would be bad. + var clone = this._cloneNode(html); + if(node.parentNode){ + node.parentNode.replaceChild(clone, node); + } + //Find deepest element and insert old node in it. + var insertion = getWrapInsertion(clone); + insertion.appendChild(node); + } + } + return this; //dojo.NodeList + }, + + wrapAll: function(/*String||DOMNode*/html){ + // summary: + // Insert html where the first node in this NodeList lives, then place all + // nodes in this NodeList as the child of the html. + // returns: + // dojo.NodeList, the nodes in the current NodeList will be returned, + // not the nodes from html argument. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".red").wrapAll('
    '); + // Results in this DOM structure: + // |
    + // |
    + // |
    Red One
    + // |
    Red Two
    + // |
    + // |
    Blue One
    + // |
    Blue Two
    + // |
    + if(this[0]){ + html = makeWrapNode(html, this[0]); + + //Place the wrap HTML in place of the first node. + this[0].parentNode.replaceChild(html, this[0]); + + //Now cycle through the elements and move them inside + //the wrap. + var insertion = getWrapInsertion(html); + for(var i = 0, node; node = this[i]; i++){ + insertion.appendChild(node); + } + } + return this; //dojo.NodeList + }, + + wrapInner: function(/*String||DOMNode*/html){ + // summary: + // For each node in the NodeList, wrap all its children with the passed in html. + // description: + // html will be cloned if the NodeList has more than one + // element. Only DOM nodes are cloned, not any attached + // event handlers. + // returns: + // dojo.NodeList, the nodes in the current NodeList will be returned, + // not the nodes from html argument. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".red").wrapInner(''); + // Results in this DOM structure: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + if(this[0]){ + html = makeWrapNode(html, this[0]); + for(var i = 0; i < this.length; i++){ + //Always clone because if html is used to hold one of + //the "this" nodes, then on the clone of html it will contain + //that "this" node, and that would be bad. + var clone = this._cloneNode(html); + + //Need to convert the childNodes to an array since wrapAll modifies the + //DOM and can change the live childNodes NodeList. + this._wrap(dojo._toArray(this[i].childNodes), null, this._NodeListCtor).wrapAll(clone); + } + } + return this; //dojo.NodeList + }, + + replaceWith: function(/*String||DOMNode||NodeList*/content){ + // summary: + // Replaces each node in ths NodeList with the content passed to replaceWith. + // description: + // The content will be cloned if the length of NodeList + // is greater than 1. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // The nodes currently in this NodeList will be returned, not the replacing content. + // Note that the returned nodes have been removed from the DOM. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".red").replaceWith('
    Green
    '); + // Results in this DOM structure: + // |
    + // |
    Green
    + // |
    Blue One
    + // |
    Green
    + // |
    Blue Two
    + // |
    + content = this._normalize(content, this[0]); + for(var i = 0, node; node = this[i]; i++){ + this._place(content, node, "before", i > 0); + node.parentNode.removeChild(node); + } + return this; //dojo.NodeList + }, + + replaceAll: function(/*String*/query){ + // summary: + // replaces nodes matched by the query passed to replaceAll with the nodes + // in this NodeList. + // description: + // The nodes in this NodeList will be cloned if the query + // matches more than one element. Only the DOM nodes are cloned, not + // any attached event handlers. + // returns: + // The nodes currently in this NodeList will be returned, not the matched nodes + // from the query. The nodes currently in this NodeLIst could have + // been cloned, so the returned NodeList will include the cloned nodes. + // example: + // assume a DOM created by this markup: + // |
    + // |
    ___
    + // |
    Red One
    + // |
    ___
    + // |
    Blue One
    + // |
    ___
    + // |
    Red Two
    + // |
    ___
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".red").replaceAll(".blue"); + // Results in this DOM structure: + // |
    + // |
    ___
    + // |
    ___
    + // |
    Red One
    + // |
    Red Two
    + // |
    ___
    + // |
    ___
    + // |
    Red One
    + // |
    Red Two
    + // |
    + var nl = dojo.query(query); + var content = this._normalize(this, this[0]); + for(var i = 0, node; node = nl[i]; i++){ + this._place(content, node, "before", i > 0); + node.parentNode.removeChild(node); + } + return this; //dojo.NodeList + }, + + clone: function(){ + // summary: + // Clones all the nodes in this NodeList and returns them as a new NodeList. + // description: + // Only the DOM nodes are cloned, not any attached event handlers. + // returns: + // dojo.NodeList, a cloned set of the original nodes. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".red").clone().appendTo(".container"); + // Results in this DOM structure: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    Red One
    + // |
    Red Two
    + // |
    + + //TODO: need option to clone events? + var ary = []; + for(var i = 0; i < this.length; i++){ + ary.push(this._cloneNode(this[i])); + } + return this._wrap(ary, this, this._NodeListCtor); //dojo.NodeList + } + }); + + //set up html method if one does not exist + if(!dojo.NodeList.prototype.html){ + dojo.NodeList.prototype.html = dojo.NodeList.prototype.innerHTML; + } })(); + } diff --git a/lib/dojo/NodeList-traverse.js b/lib/dojo/NodeList-traverse.js index 4fda2e7e..b5314eed 100644 --- a/lib/dojo/NodeList-traverse.js +++ b/lib/dojo/NodeList-traverse.js @@ -5,127 +5,513 @@ */ -if(!dojo._hasResource["dojo.NodeList-traverse"]){ -dojo._hasResource["dojo.NodeList-traverse"]=true; +if(!dojo._hasResource["dojo.NodeList-traverse"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.NodeList-traverse"] = true; dojo.provide("dojo.NodeList-traverse"); -dojo.extend(dojo.NodeList,{_buildArrayFromCallback:function(_1){ -var _2=[]; -for(var i=0;i + // |
    Red One
    + // | Some Text + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // | + // Running this code: + // | dojo.query(".container").children(); + // returns the four divs that are children of the container div. + // Running this code: + // | dojo.query(".container").children(".red"); + // returns the two divs that have the class "red". + return this._getRelatedUniqueNodes(query, function(node, ary){ + return dojo._toArray(node.childNodes); + }); //dojo.NodeList + }, + + closest: function(/*String*/query){ + // summary: + // Returns closest parent that matches query, including current node in this + // dojo.NodeList if it matches the query. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // query: + // a CSS selector. + // returns: + // dojo.NodeList, the closest parent that matches the query, including the current + // node in this dojo.NodeList if it matches the query. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // | Some Text + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".red").closest(".container"); + // returns the div with class "container". + var self = this; + return this._getRelatedUniqueNodes(query, function(node, ary){ + do{ + if(self._filterQueryResult([node], query).length){ + return node; + } + }while((node = node.parentNode) && node.nodeType == 1); + return null; //To make rhino strict checking happy. + }); //dojo.NodeList + }, + + parent: function(/*String?*/query){ + // summary: + // Returns immediate parent elements for nodes in this dojo.NodeList. + // Optionally takes a query to filter the parent elements. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // query: + // a CSS selector. + // returns: + // dojo.NodeList, immediate parent elements for nodes in this dojo.NodeList. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".text").parent(); + // returns the two divs with class "blue". + // Running this code: + // | dojo.query(".text").parent(".first"); + // returns the one div with class "blue" and "first". + return this._getRelatedUniqueNodes(query, function(node, ary){ + return node.parentNode; + }); //dojo.NodeList + }, + + parents: function(/*String?*/query){ + // summary: + // Returns all parent elements for nodes in this dojo.NodeList. + // Optionally takes a query to filter the child elements. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // query: + // a CSS selector. + // returns: + // dojo.NodeList, all parent elements for nodes in this dojo.NodeList. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".text").parents(); + // returns the two divs with class "blue", the div with class "container", + // | the body element and the html element. + // Running this code: + // | dojo.query(".text").parents(".container"); + // returns the one div with class "container". + return this._getRelatedUniqueNodes(query, function(node, ary){ + var pary = [] + while(node.parentNode){ + node = node.parentNode; + pary.push(node); + } + return pary; + }); //dojo.NodeList + }, + + siblings: function(/*String?*/query){ + // summary: + // Returns all sibling elements for nodes in this dojo.NodeList. + // Optionally takes a query to filter the sibling elements. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // query: + // a CSS selector. + // returns: + // dojo.NodeList, all sibling elements for nodes in this dojo.NodeList. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // | Some Text + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".first").siblings(); + // returns the two divs with class "red" and the other div + // | with class "blue" that does not have "first". + // Running this code: + // | dojo.query(".first").siblings(".red"); + // returns the two div with class "red". + return this._getRelatedUniqueNodes(query, function(node, ary){ + var pary = [] + var nodes = (node.parentNode && node.parentNode.childNodes); + for(var i = 0; i < nodes.length; i++){ + if(nodes[i] != node){ + pary.push(nodes[i]); + } + } + return pary; + }); //dojo.NodeList + }, + + next: function(/*String?*/query){ + // summary: + // Returns the next element for nodes in this dojo.NodeList. + // Optionally takes a query to filter the next elements. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // query: + // a CSS selector. + // returns: + // dojo.NodeList, the next element for nodes in this dojo.NodeList. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // | Some Text + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".first").next(); + // returns the div with class "red" and has innerHTML of "Red Two". + // Running this code: + // | dojo.query(".last").next(".red"); + // does not return any elements. + return this._getRelatedUniqueNodes(query, function(node, ary){ + var next = node.nextSibling; + while(next && next.nodeType != 1){ + next = next.nextSibling; + } + return next; + }); //dojo.NodeList + }, + + nextAll: function(/*String?*/query){ + // summary: + // Returns all sibling elements that come after the nodes in this dojo.NodeList. + // Optionally takes a query to filter the sibling elements. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // query: + // a CSS selector. + // returns: + // dojo.NodeList, all sibling elements that come after the nodes in this dojo.NodeList. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // | Some Text + // |
    Blue One
    + // | + // | + // |
    + // Running this code: + // | dojo.query(".first").nextAll(); + // returns the two divs with class of "next". + // Running this code: + // | dojo.query(".first").nextAll(".red"); + // returns the one div with class "red" and innerHTML "Red Two". + return this._getRelatedUniqueNodes(query, function(node, ary){ + var pary = [] + var next = node; + while((next = next.nextSibling)){ + if(next.nodeType == 1){ + pary.push(next); + } + } + return pary; + }); //dojo.NodeList + }, + + prev: function(/*String?*/query){ + // summary: + // Returns the previous element for nodes in this dojo.NodeList. + // Optionally takes a query to filter the previous elements. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // query: + // a CSS selector. + // returns: + // dojo.NodeList, the previous element for nodes in this dojo.NodeList. + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // | Some Text + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".first").prev(); + // returns the div with class "red" and has innerHTML of "Red One". + // Running this code: + // | dojo.query(".first").prev(".blue"); + // does not return any elements. + return this._getRelatedUniqueNodes(query, function(node, ary){ + var prev = node.previousSibling; + while(prev && prev.nodeType != 1){ + prev = prev.previousSibling; + } + return prev; + }); //dojo.NodeList + }, + + prevAll: function(/*String?*/query){ + // summary: + // Returns all sibling elements that come before the nodes in this dojo.NodeList. + // Optionally takes a query to filter the sibling elements. + // description: + // The returned nodes will be in reverse DOM order -- the first node in the list will + // be the node closest to the original node/NodeList. + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // query: + // a CSS selector. + // returns: + // dojo.NodeList, all sibling elements that come before the nodes in this dojo.NodeList. + // example: + // assume a DOM created by this markup: + // |
    + // | + // | Some Text + // | + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".second").prevAll(); + // returns the two divs with class of "prev". + // Running this code: + // | dojo.query(".first").prevAll(".red"); + // returns the one div with class "red prev" and innerHTML "Red One". + return this._getRelatedUniqueNodes(query, function(node, ary){ + var pary = [] + var prev = node; + while((prev = prev.previousSibling)){ + if(prev.nodeType == 1){ + pary.push(prev); + } + } + return pary; + }); //dojo.NodeList + }, + + andSelf: function(){ + // summary: + // Adds the nodes from the previous dojo.NodeList to the current dojo.NodeList. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // returns: + // dojo.NodeList + // example: + // assume a DOM created by this markup: + // |
    + // | + // | Some Text + // | + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".second").prevAll().andSelf(); + // returns the two divs with class of "prev", as well as the div with class "second". + return this.concat(this._parent); + }, + + //Alternate methods for the :first/:last/:even/:odd pseudos. + first: function(){ + // summary: + // Returns the first node in this dojo.NodeList as a dojo.NodeList. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // returns: + // dojo.NodeList, with the first node in this dojo.NodeList + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".blue").first(); + // returns the div with class "blue" and "first". + return this._wrap(((this[0] && [this[0]]) || []), this); //dojo.NodeList + }, + + last: function(){ + // summary: + // Returns the last node in this dojo.NodeList as a dojo.NodeList. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // returns: + // dojo.NodeList, with the last node in this dojo.NodeList + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".blue").last(); + // returns the last div with class "blue", + return this._wrap((this.length ? [this[this.length - 1]] : []), this); //dojo.NodeList + }, + + even: function(){ + // summary: + // Returns the even nodes in this dojo.NodeList as a dojo.NodeList. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // returns: + // dojo.NodeList, with the even nodes in this dojo.NodeList + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".interior").even(); + // returns the two divs with class "blue" + return this.filter(function(item, i){ + return i % 2 != 0; + }); //dojo.NodeList + }, + + odd: function(){ + // summary: + // Returns the odd nodes in this dojo.NodeList as a dojo.NodeList. + // description: + // .end() can be used on the returned dojo.NodeList to get back to the + // original dojo.NodeList. + // returns: + // dojo.NodeList, with the odd nodes in this dojo.NodeList + // example: + // assume a DOM created by this markup: + // |
    + // |
    Red One
    + // |
    Blue One
    + // |
    Red Two
    + // |
    Blue Two
    + // |
    + // Running this code: + // | dojo.query(".interior").odd(); + // returns the two divs with class "red" + return this.filter(function(item, i){ + return i % 2 == 0; + }); //dojo.NodeList + } }); -}}); + } diff --git a/lib/dojo/OpenAjax.js b/lib/dojo/OpenAjax.js index a33e67d5..c777f3ea 100644 --- a/lib/dojo/OpenAjax.js +++ b/lib/dojo/OpenAjax.js @@ -5,151 +5,197 @@ */ +/******************************************************************************* + * OpenAjax.js + * + * Reference implementation of the OpenAjax Hub, as specified by OpenAjax Alliance. + * Specification is under development at: + * + * http://www.openajax.org/member/wiki/OpenAjax_Hub_Specification + * + * Copyright 2006-2007 OpenAjax Alliance + * + * Licensed 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. + * + ******************************************************************************/ + +// prevent re-definition of the OpenAjax object if(!window["OpenAjax"]){ -OpenAjax=new function(){ -var t=true; -var f=false; -var g=window; -var _1; -var _2="org.openajax.hub."; -var h={}; -this.hub=h; -h.implementer="http://openajax.org"; -h.implVersion="0.6"; -h.specVersion="0.6"; -h.implExtraData={}; -var _1={}; -h.libraries=_1; -h.registerLibrary=function(_3,_4,_5,_6){ -_1[_3]={prefix:_3,namespaceURI:_4,version:_5,extraData:_6}; -this.publish(_2+"registerLibrary",_1[_3]); -}; -h.unregisterLibrary=function(_7){ -this.publish(_2+"unregisterLibrary",_1[_7]); -delete _1[_7]; -}; -h._subscriptions={c:{},s:[]}; -h._cleanup=[]; -h._subIndex=0; -h._pubDepth=0; -h.subscribe=function(_8,_9,_a,_b,_c){ -if(!_a){ -_a=window; -} -var _d=_8+"."+this._subIndex; -var _e={scope:_a,cb:_9,fcb:_c,data:_b,sid:this._subIndex++,hdl:_d}; -var _f=_8.split("."); -this._subscribe(this._subscriptions,_f,0,_e); -return _d; -}; -h.publish=function(_10,_11){ -var _12=_10.split("."); -this._pubDepth++; -this._publish(this._subscriptions,_12,0,_10,_11); -this._pubDepth--; -if((this._cleanup.length>0)&&(this._pubDepth==0)){ -for(var i=0;i0){ -_22[i].cb=null; -this._cleanup.push(_22[i]); -}else{ -_22.splice(i,1); -} -return; -} -} -} -} -}; -h.reinit=function(){ -for(var lib in OpenAjax.hub.libraries){ -delete OpenAjax.hub.libraries[lib]; -} -OpenAjax.hub.registerLibrary("OpenAjax","http://openajax.org/hub","0.6",{}); -delete OpenAjax._subscriptions; -OpenAjax._subscriptions={c:{},s:[]}; -delete OpenAjax._cleanup; -OpenAjax._cleanup=[]; -OpenAjax._subIndex=0; -OpenAjax._pubDepth=0; -}; -}; -OpenAjax.hub.registerLibrary("OpenAjax","http://openajax.org/hub","0.6",{}); + OpenAjax = new function(){ + // summary: the OpenAjax hub + // description: see http://www.openajax.org/member/wiki/OpenAjax_Hub_Specification + + var t = true; + var f = false; + var g = window; + var libs; + var ooh = "org.openajax.hub."; + + var h = {}; + this.hub = h; + h.implementer = "http://openajax.org"; + h.implVersion = "0.6"; + h.specVersion = "0.6"; + h.implExtraData = {}; + var libs = {}; + h.libraries = libs; + + h.registerLibrary = function(prefix, nsURL, version, extra){ + libs[prefix] = { + prefix: prefix, + namespaceURI: nsURL, + version: version, + extraData: extra + }; + this.publish(ooh+"registerLibrary", libs[prefix]); + } + h.unregisterLibrary = function(prefix){ + this.publish(ooh+"unregisterLibrary", libs[prefix]); + delete libs[prefix]; + } + + h._subscriptions = { c:{}, s:[] }; + h._cleanup = []; + h._subIndex = 0; + h._pubDepth = 0; + + h.subscribe = function(name, callback, scope, subscriberData, filter){ + if(!scope){ + scope = window; + } + var handle = name + "." + this._subIndex; + var sub = { scope: scope, cb: callback, fcb: filter, data: subscriberData, sid: this._subIndex++, hdl: handle }; + var path = name.split("."); + this._subscribe(this._subscriptions, path, 0, sub); + return handle; + } + + h.publish = function(name, message){ + var path = name.split("."); + this._pubDepth++; + this._publish(this._subscriptions, path, 0, name, message); + this._pubDepth--; + if((this._cleanup.length > 0) && (this._pubDepth == 0)){ + for(var i = 0; i < this._cleanup.length; i++){ + this.unsubscribe(this._cleanup[i].hdl); + } + delete(this._cleanup); + this._cleanup = []; + } + } + + h.unsubscribe = function(sub){ + var path = sub.split("."); + var sid = path.pop(); + this._unsubscribe(this._subscriptions, path, 0, sid); + } + + h._subscribe = function(tree, path, index, sub){ + var token = path[index]; + if(index == path.length){ + tree.s.push(sub); + }else{ + if(typeof tree.c == "undefined"){ + tree.c = {}; + } + if(typeof tree.c[token] == "undefined"){ + tree.c[token] = { c: {}, s: [] }; + this._subscribe(tree.c[token], path, index + 1, sub); + }else{ + this._subscribe( tree.c[token], path, index + 1, sub); + } + } + } + + h._publish = function(tree, path, index, name, msg){ + if(typeof tree != "undefined"){ + var node; + if(index == path.length) { + node = tree; + }else{ + this._publish(tree.c[path[index]], path, index + 1, name, msg); + this._publish(tree.c["*"], path, index + 1, name, msg); + node = tree.c["**"]; + } + if(typeof node != "undefined"){ + var callbacks = node.s; + var max = callbacks.length; + for(var i = 0; i < max; i++){ + if(callbacks[i].cb){ + var sc = callbacks[i].scope; + var cb = callbacks[i].cb; + var fcb = callbacks[i].fcb; + var d = callbacks[i].data; + if(typeof cb == "string"){ + // get a function object + cb = sc[cb]; + } + if(typeof fcb == "string"){ + // get a function object + fcb = sc[fcb]; + } + if((!fcb) || + (fcb.call(sc, name, msg, d))) { + cb.call(sc, name, msg, d); + } + } + } + } + } + } + + h._unsubscribe = function(tree, path, index, sid) { + if(typeof tree != "undefined") { + if(index < path.length) { + var childNode = tree.c[path[index]]; + this._unsubscribe(childNode, path, index + 1, sid); + if(childNode.s.length == 0) { + for(var x in childNode.c) + return; + delete tree.c[path[index]]; + } + return; + } + else { + var callbacks = tree.s; + var max = callbacks.length; + for(var i = 0; i < max; i++) + if(sid == callbacks[i].sid) { + if(this._pubDepth > 0) { + callbacks[i].cb = null; + this._cleanup.push(callbacks[i]); + } + else + callbacks.splice(i, 1); + return; + } + } + } + } + // The following function is provided for automatic testing purposes. + // It is not expected to be deployed in run-time OpenAjax Hub implementations. + h.reinit = function() + { + for (var lib in OpenAjax.hub.libraries) { + delete OpenAjax.hub.libraries[lib]; + } + OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "0.6", {}); + + delete OpenAjax._subscriptions; + OpenAjax._subscriptions = {c:{},s:[]}; + delete OpenAjax._cleanup; + OpenAjax._cleanup = []; + OpenAjax._subIndex = 0; + OpenAjax._pubDepth = 0; + } + }; + // Register the OpenAjax Hub itself as a library. + OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "0.6", {}); + } diff --git a/lib/dojo/Stateful.js b/lib/dojo/Stateful.js index 136b319c..e4f40630 100644 --- a/lib/dojo/Stateful.js +++ b/lib/dojo/Stateful.js @@ -5,60 +5,130 @@ */ -if(!dojo._hasResource["dojo.Stateful"]){ -dojo._hasResource["dojo.Stateful"]=true; +if(!dojo._hasResource["dojo.Stateful"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.Stateful"] = true; dojo.provide("dojo.Stateful"); -dojo.declare("dojo.Stateful",null,{postscript:function(_1){ -if(_1){ -dojo.mixin(this,_1); -} -},get:function(_2){ -return this[_2]; -},set:function(_3,_4){ -if(typeof _3==="object"){ -for(var x in _3){ -this.set(x,_3[x]); -} -return this; -} -var _5=this[_3]; -this[_3]=_4; -if(this._watchCallbacks){ -this._watchCallbacks(_3,_5,_4); -} -return this; -},watch:function(_6,_7){ -var _8=this._watchCallbacks; -if(!_8){ -var _9=this; -_8=this._watchCallbacks=function(_a,_b,_c,_d){ -var _e=function(_f){ -for(var i=0,l=_f&&_f.length;i>=_e; -t[x]=_e==4?17*c:c; -}); -t.a=1; -return t; -}; -dojo.colorFromArray=function(a,obj){ -var t=obj||new d.Color(); -t._set(Number(a[0]),Number(a[1]),Number(a[2]),Number(a[3])); -if(isNaN(t.a)){ -t.a=1; -} -return t.sanitize(); -}; -dojo.colorFromString=function(str,obj){ -var a=d.Color.named[str]; -return a&&d.colorFromArray(a,obj)||d.colorFromRgb(str,obj)||d.colorFromHex(str,obj); -}; + + var d = dojo; + + dojo.Color = function(/*Array|String|Object*/ color){ + // summary: + // Takes a named string, hex string, array of rgb or rgba values, + // an object with r, g, b, and a properties, or another `dojo.Color` object + // and creates a new Color instance to work from. + // + // example: + // Work with a Color instance: + // | var c = new dojo.Color(); + // | c.setColor([0,0,0]); // black + // | var hex = c.toHex(); // #000000 + // + // example: + // Work with a node's color: + // | var color = dojo.style("someNode", "backgroundColor"); + // | var n = new dojo.Color(color); + // | // adjust the color some + // | n.r *= .5; + // | console.log(n.toString()); // rgb(128, 255, 255); + if(color){ this.setColor(color); } + }; + + // FIXME: + // there's got to be a more space-efficient way to encode or discover + // these!! Use hex? + dojo.Color.named = { + black: [0,0,0], + silver: [192,192,192], + gray: [128,128,128], + white: [255,255,255], + maroon: [128,0,0], + red: [255,0,0], + purple: [128,0,128], + fuchsia: [255,0,255], + green: [0,128,0], + lime: [0,255,0], + olive: [128,128,0], + yellow: [255,255,0], + navy: [0,0,128], + blue: [0,0,255], + teal: [0,128,128], + aqua: [0,255,255], + transparent: d.config.transparentColor || [255,255,255] + }; + + dojo.extend(dojo.Color, { + r: 255, g: 255, b: 255, a: 1, + _set: function(r, g, b, a){ + var t = this; t.r = r; t.g = g; t.b = b; t.a = a; + }, + setColor: function(/*Array|String|Object*/ color){ + // summary: + // Takes a named string, hex string, array of rgb or rgba values, + // an object with r, g, b, and a properties, or another `dojo.Color` object + // and sets this color instance to that value. + // + // example: + // | var c = new dojo.Color(); // no color + // | c.setColor("#ededed"); // greyish + if(d.isString(color)){ + d.colorFromString(color, this); + }else if(d.isArray(color)){ + d.colorFromArray(color, this); + }else{ + this._set(color.r, color.g, color.b, color.a); + if(!(color instanceof d.Color)){ this.sanitize(); } + } + return this; // dojo.Color + }, + sanitize: function(){ + // summary: + // Ensures the object has correct attributes + // description: + // the default implementation does nothing, include dojo.colors to + // augment it with real checks + return this; // dojo.Color + }, + toRgb: function(){ + // summary: + // Returns 3 component array of rgb values + // example: + // | var c = new dojo.Color("#000000"); + // | console.log(c.toRgb()); // [0,0,0] + var t = this; + return [t.r, t.g, t.b]; // Array + }, + toRgba: function(){ + // summary: + // Returns a 4 component array of rgba values from the color + // represented by this object. + var t = this; + return [t.r, t.g, t.b, t.a]; // Array + }, + toHex: function(){ + // summary: + // Returns a CSS color string in hexadecimal representation + // example: + // | console.log(new dojo.Color([0,0,0]).toHex()); // #000000 + var arr = d.map(["r", "g", "b"], function(x){ + var s = this[x].toString(16); + return s.length < 2 ? "0" + s : s; + }, this); + return "#" + arr.join(""); // String + }, + toCss: function(/*Boolean?*/ includeAlpha){ + // summary: + // Returns a css color string in rgb(a) representation + // example: + // | var c = new dojo.Color("#FFF").toCss(); + // | console.log(c); // rgb('255','255','255') + var t = this, rgb = t.r + ", " + t.g + ", " + t.b; + return (includeAlpha ? "rgba(" + rgb + ", " + t.a : "rgb(" + rgb) + ")"; // String + }, + toString: function(){ + // summary: + // Returns a visual representation of the color + return this.toCss(true); // String + } + }); + + dojo.blendColors = function( + /*dojo.Color*/ start, + /*dojo.Color*/ end, + /*Number*/ weight, + /*dojo.Color?*/ obj + ){ + // summary: + // Blend colors end and start with weight from 0 to 1, 0.5 being a 50/50 blend, + // can reuse a previously allocated dojo.Color object for the result + var t = obj || new d.Color(); + d.forEach(["r", "g", "b", "a"], function(x){ + t[x] = start[x] + (end[x] - start[x]) * weight; + if(x != "a"){ t[x] = Math.round(t[x]); } + }); + return t.sanitize(); // dojo.Color + }; + + dojo.colorFromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){ + // summary: + // Returns a `dojo.Color` instance from a string of the form + // "rgb(...)" or "rgba(...)". Optionally accepts a `dojo.Color` + // object to update with the parsed value and return instead of + // creating a new object. + // returns: + // A dojo.Color object. If obj is passed, it will be the return value. + var m = color.toLowerCase().match(/^rgba?\(([\s\.,0-9]+)\)/); + return m && dojo.colorFromArray(m[1].split(/\s*,\s*/), obj); // dojo.Color + }; + + dojo.colorFromHex = function(/*String*/ color, /*dojo.Color?*/ obj){ + // summary: + // Converts a hex string with a '#' prefix to a color object. + // Supports 12-bit #rgb shorthand. Optionally accepts a + // `dojo.Color` object to update with the parsed value. + // + // returns: + // A dojo.Color object. If obj is passed, it will be the return value. + // + // example: + // | var thing = dojo.colorFromHex("#ededed"); // grey, longhand + // + // example: + // | var thing = dojo.colorFromHex("#000"); // black, shorthand + var t = obj || new d.Color(), + bits = (color.length == 4) ? 4 : 8, + mask = (1 << bits) - 1; + color = Number("0x" + color.substr(1)); + if(isNaN(color)){ + return null; // dojo.Color + } + d.forEach(["b", "g", "r"], function(x){ + var c = color & mask; + color >>= bits; + t[x] = bits == 4 ? 17 * c : c; + }); + t.a = 1; + return t; // dojo.Color + }; + + dojo.colorFromArray = function(/*Array*/ a, /*dojo.Color?*/ obj){ + // summary: + // Builds a `dojo.Color` from a 3 or 4 element array, mapping each + // element in sequence to the rgb(a) values of the color. + // example: + // | var myColor = dojo.colorFromArray([237,237,237,0.5]); // grey, 50% alpha + // returns: + // A dojo.Color object. If obj is passed, it will be the return value. + var t = obj || new d.Color(); + t._set(Number(a[0]), Number(a[1]), Number(a[2]), Number(a[3])); + if(isNaN(t.a)){ t.a = 1; } + return t.sanitize(); // dojo.Color + }; + + dojo.colorFromString = function(/*String*/ str, /*dojo.Color?*/ obj){ + // summary: + // Parses `str` for a color value. Accepts hex, rgb, and rgba + // style color values. + // description: + // Acceptable input values for str may include arrays of any form + // accepted by dojo.colorFromArray, hex strings such as "#aaaaaa", or + // rgb or rgba strings such as "rgb(133, 200, 16)" or "rgba(10, 10, + // 10, 50)" + // returns: + // A dojo.Color object. If obj is passed, it will be the return value. + var a = d.Color.named[str]; + return a && d.colorFromArray(a, obj) || d.colorFromRgb(str, obj) || d.colorFromHex(str, obj); + }; })(); + } diff --git a/lib/dojo/_base/Deferred.js b/lib/dojo/_base/Deferred.js index dfbabc4c..3193024a 100644 --- a/lib/dojo/_base/Deferred.js +++ b/lib/dojo/_base/Deferred.js @@ -5,126 +5,338 @@ */ -if(!dojo._hasResource["dojo._base.Deferred"]){ -dojo._hasResource["dojo._base.Deferred"]=true; +if(!dojo._hasResource["dojo._base.Deferred"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.Deferred"] = true; dojo.provide("dojo._base.Deferred"); dojo.require("dojo._base.lang"); + (function(){ -var _1=function(){ -}; -var _2=Object.freeze||function(){ -}; -dojo.Deferred=function(_3){ -var _4,_5,_6,_7,_8; -var _9=this.promise={}; -function _a(_b){ -if(_5){ -throw new Error("This deferred has already been resolved"); -} -_4=_b; -_5=true; -_c(); -}; -function _c(){ -var _d; -while(!_d&&_8){ -var _e=_8; -_8=_8.next; -if(_d=(_e.progress==_1)){ -_5=false; -} -var _f=(_6?_e.error:_e.resolved); -if(_f){ -try{ -var _10=_f(_4); -if(_10&&typeof _10.then==="function"){ -_10.then(dojo.hitch(_e.deferred,"resolve"),dojo.hitch(_e.deferred,"reject")); -continue; -} -var _11=_d&&_10===undefined; -_e.deferred[_11&&_6?"reject":"resolve"](_11?_4:_10); -} -catch(e){ -_e.deferred.reject(e); -} -}else{ -if(_6){ -_e.deferred.reject(_4); -}else{ -_e.deferred.resolve(_4); -} -} -} -}; -this.resolve=this.callback=function(_12){ -this.fired=0; -this.results=[_12,null]; -_a(_12); -}; -this.reject=this.errback=function(_13){ -_6=true; -this.fired=1; -_a(_13); -this.results=[null,_13]; -if(!_13||_13.log!==false){ -(dojo.config.deferredOnError||function(x){ -console.error(x); -})(_13); -} -}; -this.progress=function(_14){ -var _15=_8; -while(_15){ -var _16=_15.progress; -_16&&_16(_14); -_15=_15.next; -} -}; -this.addCallbacks=function(_17,_18){ -this.then(_17,_18,_1); -return this; -}; -this.then=_9.then=function(_19,_1a,_1b){ -var _1c=_1b==_1?this:new dojo.Deferred(_9.cancel); -var _1d={resolved:_19,error:_1a,progress:_1b,deferred:_1c}; -if(_8){ -_7=_7.next=_1d; -}else{ -_8=_7=_1d; -} -if(_5){ -_c(); -} -return _1c.promise; -}; -var _1e=this; -this.cancel=_9.cancel=function(){ -if(!_5){ -var _1f=_3&&_3(_1e); -if(!_5){ -if(!(_1f instanceof Error)){ -_1f=new Error(_1f); -} -_1f.log=false; -_1e.reject(_1f); -} -} -}; -_2(_9); -}; -dojo.extend(dojo.Deferred,{addCallback:function(_20){ -return this.addCallbacks(dojo.hitch.apply(dojo,arguments)); -},addErrback:function(_21){ -return this.addCallbacks(null,dojo.hitch.apply(dojo,arguments)); -},addBoth:function(_22){ -var _23=dojo.hitch.apply(dojo,arguments); -return this.addCallbacks(_23,_23); -},fired:-1}); + var mutator = function(){}; + var freeze = Object.freeze || function(){}; + // A deferred provides an API for creating and resolving a promise. + dojo.Deferred = function(/*Function?*/canceller){ + // summary: + // Deferreds provide a generic means for encapsulating an asynchronous + // operation and notifying users of the completion and result of the operation. + // description: + // The dojo.Deferred API is based on the concept of promises that provide a + // generic interface into the eventual completion of an asynchronous action. + // The motivation for promises fundamentally is about creating a + // separation of concerns that allows one to achieve the same type of + // call patterns and logical data flow in asynchronous code as can be + // achieved in synchronous code. Promises allows one + // to be able to call a function purely with arguments needed for + // execution, without conflating the call with concerns of whether it is + // sync or async. One shouldn't need to alter a call's arguments if the + // implementation switches from sync to async (or vice versa). By having + // async functions return promises, the concerns of making the call are + // separated from the concerns of asynchronous interaction (which are + // handled by the promise). + // + // The dojo.Deferred is a type of promise that provides methods for fulfilling the + // promise with a successful result or an error. The most important method for + // working with Dojo's promises is the then() method, which follows the + // CommonJS proposed promise API. An example of using a Dojo promise: + // + // | var resultingPromise = someAsyncOperation.then(function(result){ + // | ... handle result ... + // | }, + // | function(error){ + // | ... handle error ... + // | }); + // + // The .then() call returns a new promise that represents the result of the + // execution of the callback. The callbacks will never affect the original promises value. + // + // The dojo.Deferred instances also provide the following functions for backwards compatibility: + // + // * addCallback(handler) + // * addErrback(handler) + // * callback(result) + // * errback(result) + // + // Callbacks are allowed to return promisesthemselves, so + // you can build complicated sequences of events with ease. + // + // The creator of the Deferred may specify a canceller. The canceller + // is a function that will be called if Deferred.cancel is called + // before the Deferred fires. You can use this to implement clean + // aborting of an XMLHttpRequest, etc. Note that cancel will fire the + // deferred with a CancelledError (unless your canceller returns + // another kind of error), so the errbacks should be prepared to + // handle that error for cancellable Deferreds. + // example: + // | var deferred = new dojo.Deferred(); + // | setTimeout(function(){ deferred.callback({success: true}); }, 1000); + // | return deferred; + // example: + // Deferred objects are often used when making code asynchronous. It + // may be easiest to write functions in a synchronous manner and then + // split code using a deferred to trigger a response to a long-lived + // operation. For example, instead of register a callback function to + // denote when a rendering operation completes, the function can + // simply return a deferred: + // + // | // callback style: + // | function renderLotsOfData(data, callback){ + // | var success = false + // | try{ + // | for(var x in data){ + // | renderDataitem(data[x]); + // | } + // | success = true; + // | }catch(e){ } + // | if(callback){ + // | callback(success); + // | } + // | } + // + // | // using callback style + // | renderLotsOfData(someDataObj, function(success){ + // | // handles success or failure + // | if(!success){ + // | promptUserToRecover(); + // | } + // | }); + // | // NOTE: no way to add another callback here!! + // example: + // Using a Deferred doesn't simplify the sending code any, but it + // provides a standard interface for callers and senders alike, + // providing both with a simple way to service multiple callbacks for + // an operation and freeing both sides from worrying about details + // such as "did this get called already?". With Deferreds, new + // callbacks can be added at any time. + // + // | // Deferred style: + // | function renderLotsOfData(data){ + // | var d = new dojo.Deferred(); + // | try{ + // | for(var x in data){ + // | renderDataitem(data[x]); + // | } + // | d.callback(true); + // | }catch(e){ + // | d.errback(new Error("rendering failed")); + // | } + // | return d; + // | } + // + // | // using Deferred style + // | renderLotsOfData(someDataObj).then(null, function(){ + // | promptUserToRecover(); + // | }); + // | // NOTE: addErrback and addCallback both return the Deferred + // | // again, so we could chain adding callbacks or save the + // | // deferred for later should we need to be notified again. + // example: + // In this example, renderLotsOfData is syncrhonous and so both + // versions are pretty artificial. Putting the data display on a + // timeout helps show why Deferreds rock: + // + // | // Deferred style and async func + // | function renderLotsOfData(data){ + // | var d = new dojo.Deferred(); + // | setTimeout(function(){ + // | try{ + // | for(var x in data){ + // | renderDataitem(data[x]); + // | } + // | d.callback(true); + // | }catch(e){ + // | d.errback(new Error("rendering failed")); + // | } + // | }, 100); + // | return d; + // | } + // + // | // using Deferred style + // | renderLotsOfData(someDataObj).then(null, function(){ + // | promptUserToRecover(); + // | }); + // + // Note that the caller doesn't have to change his code at all to + // handle the asynchronous case. + var result, finished, isError, head, nextListener; + var promise = this.promise = {}; + + function complete(value){ + if(finished){ + throw new Error("This deferred has already been resolved"); + } + result = value; + finished = true; + notify(); + } + function notify(){ + var mutated; + while(!mutated && nextListener){ + var listener = nextListener; + nextListener = nextListener.next; + if(mutated = (listener.progress == mutator)){ // assignment and check + finished = false; + } + var func = (isError ? listener.error : listener.resolved); + if (func) { + try { + var newResult = func(result); + if (newResult && typeof newResult.then === "function") { + newResult.then(dojo.hitch(listener.deferred, "resolve"), dojo.hitch(listener.deferred, "reject")); + continue; + } + var unchanged = mutated && newResult === undefined; + listener.deferred[unchanged && isError ? "reject" : "resolve"](unchanged ? result : newResult); + } + catch (e) { + listener.deferred.reject(e); + } + }else { + if(isError){ + listener.deferred.reject(result); + }else{ + listener.deferred.resolve(result); + } + } + } + } + // calling resolve will resolve the promise + this.resolve = this.callback = function(value){ + // summary: + // Fulfills the Deferred instance successfully with the provide value + this.fired = 0; + this.results = [value, null]; + complete(value); + }; + + + // calling error will indicate that the promise failed + this.reject = this.errback = function(error){ + // summary: + // Fulfills the Deferred instance as an error with the provided error + isError = true; + this.fired = 1; + complete(error); + this.results = [null, error]; + if(!error || error.log !== false){ + (dojo.config.deferredOnError || function(x){ console.error(x); })(error); + } + }; + // call progress to provide updates on the progress on the completion of the promise + this.progress = function(update){ + // summary + // Send progress events to all listeners + var listener = nextListener; + while(listener){ + var progress = listener.progress; + progress && progress(update); + listener = listener.next; + } + }; + this.addCallbacks = function(/*Function?*/callback, /*Function?*/errback){ + this.then(callback, errback, mutator); + return this; + }; + // provide the implementation of the promise + this.then = promise.then = function(/*Function?*/resolvedCallback, /*Function?*/errorCallback, /*Function?*/progressCallback){ + // summary + // Adds a fulfilledHandler, errorHandler, and progressHandler to be called for + // completion of a promise. The fulfilledHandler is called when the promise + // is fulfilled. The errorHandler is called when a promise fails. The + // progressHandler is called for progress events. All arguments are optional + // and non-function values are ignored. The progressHandler is not only an + // optional argument, but progress events are purely optional. Promise + // providers are not required to ever create progress events. + // + // This function will return a new promise that is fulfilled when the given + // fulfilledHandler or errorHandler callback is finished. This allows promise + // operations to be chained together. The value returned from the callback + // handler is the fulfillment value for the returned promise. If the callback + // throws an error, the returned promise will be moved to failed state. + // + // example: + // An example of using a CommonJS compliant promise: + // | asyncComputeTheAnswerToEverything(). + // | then(addTwo). + // | then(printResult, onError); + // | >44 + // + var returnDeferred = progressCallback == mutator ? this : new dojo.Deferred(promise.cancel); + var listener = { + resolved: resolvedCallback, + error: errorCallback, + progress: progressCallback, + deferred: returnDeferred + }; + if(nextListener){ + head = head.next = listener; + } + else{ + nextListener = head = listener; + } + if(finished){ + notify(); + } + return returnDeferred.promise; + }; + var deferred = this; + this.cancel = promise.cancel = function () { + // summary: + // Cancels the asynchronous operation + if(!finished){ + var error = canceller && canceller(deferred); + if(!finished){ + if (!(error instanceof Error)) { + error = new Error(error); + } + error.log = false; + deferred.reject(error); + } + } + } + freeze(promise); + }; + dojo.extend(dojo.Deferred, { + addCallback: function (/*Function*/callback) { + return this.addCallbacks(dojo.hitch.apply(dojo, arguments)); + }, + + addErrback: function (/*Function*/errback) { + return this.addCallbacks(null, dojo.hitch.apply(dojo, arguments)); + }, + + addBoth: function (/*Function*/callback) { + var enclosed = dojo.hitch.apply(dojo, arguments); + return this.addCallbacks(enclosed, enclosed); + }, + fired: -1 + }); })(); -dojo.when=function(_24,_25,_26,_27){ -if(_24&&typeof _24.then==="function"){ -return _24.then(_25,_26,_27); -} -return _25(_24); +dojo.when = function(promiseOrValue, /*Function?*/callback, /*Function?*/errback, /*Function?*/progressHandler){ + // summary: + // This provides normalization between normal synchronous values and + // asynchronous promises, so you can interact with them in a common way + // example: + // | function printFirstAndList(items){ + // | dojo.when(findFirst(items), console.log); + // | dojo.when(findLast(items), console.log); + // | } + // | function findFirst(items){ + // | return dojo.when(items, function(items){ + // | return items[0]; + // | }); + // | } + // | function findLast(items){ + // | return dojo.when(items, function(items){ + // | return items[items.length]; + // | }); + // | } + // And now all three of his functions can be used sync or async. + // | printFirstAndLast([1,2,3,4]) will work just as well as + // | printFirstAndLast(dojo.xhrGet(...)); + + if(promiseOrValue && typeof promiseOrValue.then === "function"){ + return promiseOrValue.then(callback, errback, progressHandler); + } + return callback(promiseOrValue); }; + } diff --git a/lib/dojo/_base/NodeList.js b/lib/dojo/_base/NodeList.js index 6df6db97..12b631a4 100644 --- a/lib/dojo/_base/NodeList.js +++ b/lib/dojo/_base/NodeList.js @@ -5,228 +5,1012 @@ */ -if(!dojo._hasResource["dojo._base.NodeList"]){ -dojo._hasResource["dojo._base.NodeList"]=true; +if(!dojo._hasResource["dojo._base.NodeList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.NodeList"] = true; dojo.provide("dojo._base.NodeList"); dojo.require("dojo._base.lang"); dojo.require("dojo._base.array"); + (function(){ -var d=dojo; -var ap=Array.prototype,_1=ap.slice,_2=ap.concat; -var _3=function(a,_4,_5){ -if(!a.sort){ -a=_1.call(a,0); -} -var _6=_5||this._NodeListCtor||d._NodeListCtor; -a.constructor=_6; -dojo._mixin(a,_6.prototype); -a._NodeListCtor=_6; -return _4?a._stash(_4):a; -}; -var _7=function(f,a,o){ -a=[0].concat(_1.call(a,0)); -o=o||d.global; -return function(_8){ -a[0]=_8; -return f.apply(o,a); -}; -}; -var _9=function(f,o){ -return function(){ -this.forEach(_7(f,arguments,o)); -return this; -}; -}; -var _a=function(f,o){ -return function(){ -return this.map(_7(f,arguments,o)); -}; -}; -var _b=function(f,o){ -return function(){ -return this.filter(_7(f,arguments,o)); -}; -}; -var _c=function(f,g,o){ -return function(){ -var a=arguments,_d=_7(f,a,o); -if(g.call(o||d.global,a)){ -return this.map(_d); -} -this.forEach(_d); -return this; -}; -}; -var _e=function(a){ -return a.length==1&&(typeof a[0]=="string"); -}; -var _f=function(_10){ -var p=_10.parentNode; -if(p){ -p.removeChild(_10); -} -}; -dojo.NodeList=function(){ -return _3(Array.apply(null,arguments)); -}; -d._NodeListCtor=d.NodeList; -var nl=d.NodeList,nlp=nl.prototype; -nl._wrap=nlp._wrap=_3; -nl._adaptAsMap=_a; -nl._adaptAsForEach=_9; -nl._adaptAsFilter=_b; -nl._adaptWithCondition=_c; -d.forEach(["slice","splice"],function(_11){ -var f=ap[_11]; -nlp[_11]=function(){ -return this._wrap(f.apply(this,arguments),_11=="slice"?this:null); -}; -}); -d.forEach(["indexOf","lastIndexOf","every","some"],function(_12){ -var f=d[_12]; -nlp[_12]=function(){ -return f.apply(d,[this].concat(_1.call(arguments,0))); -}; -}); -d.forEach(["attr","style"],function(_13){ -nlp[_13]=_c(d[_13],_e); -}); -d.forEach(["connect","addClass","removeClass","toggleClass","empty","removeAttr"],function(_14){ -nlp[_14]=_9(d[_14]); -}); -dojo.extend(dojo.NodeList,{_normalize:function(_15,_16){ -var _17=_15.parse===true?true:false; -if(typeof _15.template=="string"){ -var _18=_15.templateFunc||(dojo.string&&dojo.string.substitute); -_15=_18?_18(_15.template,_15):_15; -} -var _19=(typeof _15); -if(_19=="string"||_19=="number"){ -_15=dojo._toDom(_15,(_16&&_16.ownerDocument)); -if(_15.nodeType==11){ -_15=dojo._toArray(_15.childNodes); -}else{ -_15=[_15]; -} -}else{ -if(!dojo.isArrayLike(_15)){ -_15=[_15]; -}else{ -if(!dojo.isArray(_15)){ -_15=dojo._toArray(_15); -} -} -} -if(_17){ -_15._runParse=true; -} -return _15; -},_cloneNode:function(_1a){ -return _1a.cloneNode(true); -},_place:function(ary,_1b,_1c,_1d){ -if(_1b.nodeType!=1&&_1c=="only"){ -return; -} -var _1e=_1b,_1f; -var _20=ary.length; -for(var i=_20-1;i>=0;i--){ -var _21=(_1d?this._cloneNode(ary[i]):ary[i]); -if(ary._runParse&&dojo.parser&&dojo.parser.parse){ -if(!_1f){ -_1f=_1e.ownerDocument.createElement("div"); -} -_1f.appendChild(_21); -dojo.parser.parse(_1f); -_21=_1f.firstChild; -while(_1f.firstChild){ -_1f.removeChild(_1f.firstChild); -} -} -if(i==_20-1){ -dojo.place(_21,_1e,_1c); -}else{ -_1e.parentNode.insertBefore(_21,_1e); -} -_1e=_21; -} -},_stash:function(_22){ -this._parent=_22; -return this; -},end:function(){ -if(this._parent){ -return this._parent; -}else{ -return new this._NodeListCtor(); -} -},concat:function(_23){ -var t=d.isArray(this)?this:_1.call(this,0),m=d.map(arguments,function(a){ -return a&&!d.isArray(a)&&(typeof NodeList!="undefined"&&a.constructor===NodeList||a.constructor===this._NodeListCtor)?_1.call(a,0):a; -}); -return this._wrap(_2.apply(t,m),this); -},map:function(_24,obj){ -return this._wrap(d.map(this,_24,obj),this); -},forEach:function(_25,_26){ -d.forEach(this,_25,_26); -return this; -},coords:_a(d.coords),position:_a(d.position),place:function(_27,_28){ -var _29=d.query(_27)[0]; -return this.forEach(function(_2a){ -d.place(_2a,_29,_28); -}); -},orphan:function(_2b){ -return (_2b?d._filterQueryResult(this,_2b):this).forEach(_f); -},adopt:function(_2c,_2d){ -return d.query(_2c).place(this[0],_2d)._stash(this); -},query:function(_2e){ -if(!_2e){ -return this; -} -var ret=this.map(function(_2f){ -return d.query(_2e,_2f).filter(function(_30){ -return _30!==undefined; -}); -}); -return this._wrap(_2.apply([],ret),this); -},filter:function(_31){ -var a=arguments,_32=this,_33=0; -if(typeof _31=="string"){ -_32=d._filterQueryResult(this,a[0]); -if(a.length==1){ -return _32._stash(this); -} -_33=1; -} -return this._wrap(d.filter(_32,a[_33],a[_33+1]),this); -},addContent:function(_34,_35){ -_34=this._normalize(_34,this[0]); -for(var i=0,_36;_36=this[i];i++){ -this._place(_34,_36,_35,i>0); -} -return this; -},instantiate:function(_37,_38){ -var c=d.isFunction(_37)?_37:d.getObject(_37); -_38=_38||{}; -return this.forEach(function(_39){ -new c(_38,_39); -}); -},at:function(){ -var t=new this._NodeListCtor(); -d.forEach(arguments,function(i){ -if(i<0){ -i=this.length+i; -} -if(this[i]){ -t.push(this[i]); -} -},this); -return t._stash(this); -}}); -nl.events=["blur","focus","change","click","error","keydown","keypress","keyup","load","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","submit"]; -d.forEach(nl.events,function(evt){ -var _3a="on"+evt; -nlp[_3a]=function(a,b){ -return this.connect(_3a,a,b); -}; -}); + + var d = dojo; + + var ap = Array.prototype, aps = ap.slice, apc = ap.concat; + + var tnl = function(/*Array*/ a, /*dojo.NodeList?*/ parent, /*Function?*/ NodeListCtor){ + // summary: + // decorate an array to make it look like a `dojo.NodeList`. + // a: + // Array of nodes to decorate. + // parent: + // An optional parent NodeList that generated the current + // list of nodes. Used to call _stash() so the parent NodeList + // can be accessed via end() later. + // NodeListCtor: + // An optional constructor function to use for any + // new NodeList calls. This allows a certain chain of + // NodeList calls to use a different object than dojo.NodeList. + if(!a.sort){ + // make sure it's a real array before we pass it on to be wrapped + a = aps.call(a, 0); + } + var ctor = NodeListCtor || this._NodeListCtor || d._NodeListCtor; + a.constructor = ctor; + dojo._mixin(a, ctor.prototype); + a._NodeListCtor = ctor; + return parent ? a._stash(parent) : a; + }; + + var loopBody = function(f, a, o){ + a = [0].concat(aps.call(a, 0)); + o = o || d.global; + return function(node){ + a[0] = node; + return f.apply(o, a); + }; + }; + + // adapters + + var adaptAsForEach = function(f, o){ + // summary: + // adapts a single node function to be used in the forEach-type + // actions. The initial object is returned from the specialized + // function. + // f: Function + // a function to adapt + // o: Object? + // an optional context for f + return function(){ + this.forEach(loopBody(f, arguments, o)); + return this; // Object + }; + }; + + var adaptAsMap = function(f, o){ + // summary: + // adapts a single node function to be used in the map-type + // actions. The return is a new array of values, as via `dojo.map` + // f: Function + // a function to adapt + // o: Object? + // an optional context for f + return function(){ + return this.map(loopBody(f, arguments, o)); + }; + }; + + var adaptAsFilter = function(f, o){ + // summary: + // adapts a single node function to be used in the filter-type actions + // f: Function + // a function to adapt + // o: Object? + // an optional context for f + return function(){ + return this.filter(loopBody(f, arguments, o)); + }; + }; + + var adaptWithCondition = function(f, g, o){ + // summary: + // adapts a single node function to be used in the map-type + // actions, behaves like forEach() or map() depending on arguments + // f: Function + // a function to adapt + // g: Function + // a condition function, if true runs as map(), otherwise runs as forEach() + // o: Object? + // an optional context for f and g + return function(){ + var a = arguments, body = loopBody(f, a, o); + if(g.call(o || d.global, a)){ + return this.map(body); // self + } + this.forEach(body); + return this; // self + }; + }; + + var magicGuard = function(a){ + // summary: + // the guard function for dojo.attr() and dojo.style() + return a.length == 1 && (typeof a[0] == "string"); // inline'd type check + }; + + var orphan = function(node){ + // summary: + // function to orphan nodes + var p = node.parentNode; + if(p){ + p.removeChild(node); + } + }; + // FIXME: should we move orphan() to dojo.html? + + dojo.NodeList = function(){ + // summary: + // dojo.NodeList is an of Array subclass which adds syntactic + // sugar for chaining, common iteration operations, animation, and + // node manipulation. NodeLists are most often returned as the + // result of dojo.query() calls. + // description: + // dojo.NodeList instances provide many utilities that reflect + // core Dojo APIs for Array iteration and manipulation, DOM + // manipulation, and event handling. Instead of needing to dig up + // functions in the dojo.* namespace, NodeLists generally make the + // full power of Dojo available for DOM manipulation tasks in a + // simple, chainable way. + // example: + // create a node list from a node + // | new dojo.NodeList(dojo.byId("foo")); + // example: + // get a NodeList from a CSS query and iterate on it + // | var l = dojo.query(".thinger"); + // | l.forEach(function(node, index, nodeList){ + // | console.log(index, node.innerHTML); + // | }); + // example: + // use native and Dojo-provided array methods to manipulate a + // NodeList without needing to use dojo.* functions explicitly: + // | var l = dojo.query(".thinger"); + // | // since NodeLists are real arrays, they have a length + // | // property that is both readable and writable and + // | // push/pop/shift/unshift methods + // | console.log(l.length); + // | l.push(dojo.create("span")); + // | + // | // dojo's normalized array methods work too: + // | console.log( l.indexOf(dojo.byId("foo")) ); + // | // ...including the special "function as string" shorthand + // | console.log( l.every("item.nodeType == 1") ); + // | + // | // NodeLists can be [..] indexed, or you can use the at() + // | // function to get specific items wrapped in a new NodeList: + // | var node = l[3]; // the 4th element + // | var newList = l.at(1, 3); // the 2nd and 4th elements + // example: + // the style functions you expect are all there too: + // | // style() as a getter... + // | var borders = dojo.query(".thinger").style("border"); + // | // ...and as a setter: + // | dojo.query(".thinger").style("border", "1px solid black"); + // | // class manipulation + // | dojo.query("li:nth-child(even)").addClass("even"); + // | // even getting the coordinates of all the items + // | var coords = dojo.query(".thinger").coords(); + // example: + // DOM manipulation functions from the dojo.* namespace area also + // available: + // | // remove all of the elements in the list from their + // | // parents (akin to "deleting" them from the document) + // | dojo.query(".thinger").orphan(); + // | // place all elements in the list at the front of #foo + // | dojo.query(".thinger").place("foo", "first"); + // example: + // Event handling couldn't be easier. `dojo.connect` is mapped in, + // and shortcut handlers are provided for most DOM events: + // | // like dojo.connect(), but with implicit scope + // | dojo.query("li").connect("onclick", console, "log"); + // | + // | // many common event handlers are already available directly: + // | dojo.query("li").onclick(console, "log"); + // | var toggleHovered = dojo.hitch(dojo, "toggleClass", "hovered"); + // | dojo.query("p") + // | .onmouseenter(toggleHovered) + // | .onmouseleave(toggleHovered); + // example: + // chainability is a key advantage of NodeLists: + // | dojo.query(".thinger") + // | .onclick(function(e){ /* ... */ }) + // | .at(1, 3, 8) // get a subset + // | .style("padding", "5px") + // | .forEach(console.log); + + return tnl(Array.apply(null, arguments)); + }; + + //Allow things that new up a NodeList to use a delegated or alternate NodeList implementation. + d._NodeListCtor = d.NodeList; + + var nl = d.NodeList, nlp = nl.prototype; + + // expose adapters and the wrapper as private functions + + nl._wrap = nlp._wrap = tnl; + nl._adaptAsMap = adaptAsMap; + nl._adaptAsForEach = adaptAsForEach; + nl._adaptAsFilter = adaptAsFilter; + nl._adaptWithCondition = adaptWithCondition; + + // mass assignment + + // add array redirectors + d.forEach(["slice", "splice"], function(name){ + var f = ap[name]; + //Use a copy of the this array via this.slice() to allow .end() to work right in the splice case. + // CANNOT apply ._stash()/end() to splice since it currently modifies + // the existing this array -- it would break backward compatibility if we copy the array before + // the splice so that we can use .end(). So only doing the stash option to this._wrap for slice. + nlp[name] = function(){ return this._wrap(f.apply(this, arguments), name == "slice" ? this : null); }; + }); + // concat should be here but some browsers with native NodeList have problems with it + + // add array.js redirectors + d.forEach(["indexOf", "lastIndexOf", "every", "some"], function(name){ + var f = d[name]; + nlp[name] = function(){ return f.apply(d, [this].concat(aps.call(arguments, 0))); }; + }); + + // add conditional methods + d.forEach(["attr", "style"], function(name){ + nlp[name] = adaptWithCondition(d[name], magicGuard); + }); + + // add forEach actions + d.forEach(["connect", "addClass", "removeClass", "toggleClass", "empty", "removeAttr"], function(name){ + nlp[name] = adaptAsForEach(d[name]); + }); + + dojo.extend(dojo.NodeList, { + _normalize: function(/*String||Element||Object||NodeList*/content, /*DOMNode?*/refNode){ + // summary: + // normalizes data to an array of items to insert. + // description: + // If content is an object, it can have special properties "template" and + // "parse". If "template" is defined, then the template value is run through + // dojo.string.substitute (if dojo.string.substitute has been dojo.required elsewhere), + // or if templateFunc is a function on the content, that function will be used to + // transform the template into a final string to be used for for passing to dojo._toDom. + // If content.parse is true, then it is remembered for later, for when the content + // nodes are inserted into the DOM. At that point, the nodes will be parsed for widgets + // (if dojo.parser has been dojo.required elsewhere). + + //Wanted to just use a DocumentFragment, but for the array/NodeList + //case that meant using cloneNode, but we may not want that. + //Cloning should only happen if the node operations span + //multiple refNodes. Also, need a real array, not a NodeList from the + //DOM since the node movements could change those NodeLists. + + var parse = content.parse === true ? true : false; + + //Do we have an object that needs to be run through a template? + if(typeof content.template == "string"){ + var templateFunc = content.templateFunc || (dojo.string && dojo.string.substitute); + content = templateFunc ? templateFunc(content.template, content) : content; + } + + var type = (typeof content); + if(type == "string" || type == "number"){ + content = dojo._toDom(content, (refNode && refNode.ownerDocument)); + if(content.nodeType == 11){ + //DocumentFragment. It cannot handle cloneNode calls, so pull out the children. + content = dojo._toArray(content.childNodes); + }else{ + content = [content]; + } + }else if(!dojo.isArrayLike(content)){ + content = [content]; + }else if(!dojo.isArray(content)){ + //To get to this point, content is array-like, but + //not an array, which likely means a DOM NodeList. Convert it now. + content = dojo._toArray(content); + } + + //Pass around the parse info + if(parse){ + content._runParse = true; + } + return content; //Array + }, + + _cloneNode: function(/*DOMNode*/ node){ + // summary: + // private utiltity to clone a node. Not very interesting in the vanilla + // dojo.NodeList case, but delegates could do interesting things like + // clone event handlers if that is derivable from the node. + return node.cloneNode(true); + }, + + _place: function(/*Array*/ary, /*DOMNode*/refNode, /*String*/position, /*Boolean*/useClone){ + // summary: + // private utility to handle placing an array of nodes relative to another node. + // description: + // Allows for cloning the nodes in the array, and for + // optionally parsing widgets, if ary._runParse is true. + + //Avoid a disallowed operation if trying to do an innerHTML on a non-element node. + if(refNode.nodeType != 1 && position == "only"){ + return; + } + var rNode = refNode, tempNode; + + //Always cycle backwards in case the array is really a + //DOM NodeList and the DOM operations take it out of the live collection. + var length = ary.length; + for(var i = length - 1; i >= 0; i--){ + var node = (useClone ? this._cloneNode(ary[i]) : ary[i]); + + //If need widget parsing, use a temp node, instead of waiting after inserting into + //real DOM because we need to start widget parsing at one node up from current node, + //which could cause some already parsed widgets to be parsed again. + if(ary._runParse && dojo.parser && dojo.parser.parse){ + if(!tempNode){ + tempNode = rNode.ownerDocument.createElement("div"); + } + tempNode.appendChild(node); + dojo.parser.parse(tempNode); + node = tempNode.firstChild; + while(tempNode.firstChild){ + tempNode.removeChild(tempNode.firstChild); + } + } + + if(i == length - 1){ + dojo.place(node, rNode, position); + }else{ + rNode.parentNode.insertBefore(node, rNode); + } + rNode = node; + } + }, + + _stash: function(parent){ + // summary: + // private function to hold to a parent NodeList. end() to return the parent NodeList. + // + // example: + // How to make a `dojo.NodeList` method that only returns the third node in + // the dojo.NodeList but allows access to the original NodeList by using this._stash: + // | dojo.extend(dojo.NodeList, { + // | third: function(){ + // | var newNodeList = dojo.NodeList(this[2]); + // | return newNodeList._stash(this); + // | } + // | }); + // | // then see how _stash applies a sub-list, to be .end()'ed out of + // | dojo.query(".foo") + // | .third() + // | .addClass("thirdFoo") + // | .end() + // | // access to the orig .foo list + // | .removeClass("foo") + // | + // + this._parent = parent; + return this; //dojo.NodeList + }, + + end: function(){ + // summary: + // Ends use of the current `dojo.NodeList` by returning the previous dojo.NodeList + // that generated the current dojo.NodeList. + // description: + // Returns the `dojo.NodeList` that generated the current `dojo.NodeList`. If there + // is no parent dojo.NodeList, an empty dojo.NodeList is returned. + // example: + // | dojo.query("a") + // | .filter(".disabled") + // | // operate on the anchors that only have a disabled class + // | .style("color", "grey") + // | .end() + // | // jump back to the list of anchors + // | .style(...) + // + if(this._parent){ + return this._parent; + }else{ + //Just return empy list. + return new this._NodeListCtor(); + } + }, + + // http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array#Methods + + // FIXME: handle return values for #3244 + // http://trac.dojotoolkit.org/ticket/3244 + + // FIXME: + // need to wrap or implement: + // join (perhaps w/ innerHTML/outerHTML overload for toString() of items?) + // reduce + // reduceRight + + /*===== + slice: function(begin, end){ + // summary: + // Returns a new NodeList, maintaining this one in place + // description: + // This method behaves exactly like the Array.slice method + // with the caveat that it returns a dojo.NodeList and not a + // raw Array. For more details, see Mozilla's (slice + // documentation)[http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:slice] + // begin: Integer + // Can be a positive or negative integer, with positive + // integers noting the offset to begin at, and negative + // integers denoting an offset from the end (i.e., to the left + // of the end) + // end: Integer? + // Optional parameter to describe what position relative to + // the NodeList's zero index to end the slice at. Like begin, + // can be positive or negative. + return this._wrap(a.slice.apply(this, arguments)); + }, + + splice: function(index, howmany, item){ + // summary: + // Returns a new NodeList, manipulating this NodeList based on + // the arguments passed, potentially splicing in new elements + // at an offset, optionally deleting elements + // description: + // This method behaves exactly like the Array.splice method + // with the caveat that it returns a dojo.NodeList and not a + // raw Array. For more details, see Mozilla's (splice + // documentation)[http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:splice] + // For backwards compatibility, calling .end() on the spliced NodeList + // does not return the original NodeList -- splice alters the NodeList in place. + // index: Integer + // begin can be a positive or negative integer, with positive + // integers noting the offset to begin at, and negative + // integers denoting an offset from the end (i.e., to the left + // of the end) + // howmany: Integer? + // Optional parameter to describe what position relative to + // the NodeList's zero index to end the slice at. Like begin, + // can be positive or negative. + // item: Object...? + // Any number of optional parameters may be passed in to be + // spliced into the NodeList + // returns: + // dojo.NodeList + return this._wrap(a.splice.apply(this, arguments)); + }, + + indexOf: function(value, fromIndex){ + // summary: + // see dojo.indexOf(). The primary difference is that the acted-on + // array is implicitly this NodeList + // value: Object: + // The value to search for. + // fromIndex: Integer?: + // The loction to start searching from. Optional. Defaults to 0. + // description: + // For more details on the behavior of indexOf, see Mozilla's + // (indexOf + // docs)[http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:indexOf] + // returns: + // Positive Integer or 0 for a match, -1 of not found. + return d.indexOf(this, value, fromIndex); // Integer + }, + + lastIndexOf: function(value, fromIndex){ + // summary: + // see dojo.lastIndexOf(). The primary difference is that the + // acted-on array is implicitly this NodeList + // description: + // For more details on the behavior of lastIndexOf, see + // Mozilla's (lastIndexOf + // docs)[http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:lastIndexOf] + // value: Object + // The value to search for. + // fromIndex: Integer? + // The loction to start searching from. Optional. Defaults to 0. + // returns: + // Positive Integer or 0 for a match, -1 of not found. + return d.lastIndexOf(this, value, fromIndex); // Integer + }, + + every: function(callback, thisObject){ + // summary: + // see `dojo.every()` and the (Array.every + // docs)[http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:every]. + // Takes the same structure of arguments and returns as + // dojo.every() with the caveat that the passed array is + // implicitly this NodeList + // callback: Function: the callback + // thisObject: Object?: the context + return d.every(this, callback, thisObject); // Boolean + }, + + some: function(callback, thisObject){ + // summary: + // Takes the same structure of arguments and returns as + // `dojo.some()` with the caveat that the passed array is + // implicitly this NodeList. See `dojo.some()` and Mozilla's + // (Array.some + // documentation)[http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:some]. + // callback: Function: the callback + // thisObject: Object?: the context + return d.some(this, callback, thisObject); // Boolean + }, + =====*/ + + concat: function(item){ + // summary: + // Returns a new NodeList comprised of items in this NodeList + // as well as items passed in as parameters + // description: + // This method behaves exactly like the Array.concat method + // with the caveat that it returns a `dojo.NodeList` and not a + // raw Array. For more details, see the (Array.concat + // docs)[http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:concat] + // item: Object? + // Any number of optional parameters may be passed in to be + // spliced into the NodeList + // returns: + // dojo.NodeList + + //return this._wrap(apc.apply(this, arguments)); + // the line above won't work for the native NodeList :-( + + // implementation notes: + // 1) Native NodeList is not an array, and cannot be used directly + // in concat() --- the latter doesn't recognize it as an array, and + // does not inline it, but append as a single entity. + // 2) On some browsers (e.g., Safari) the "constructor" property is + // read-only and cannot be changed. So we have to test for both + // native NodeList and dojo.NodeList in this property to recognize + // the node list. + + var t = d.isArray(this) ? this : aps.call(this, 0), + m = d.map(arguments, function(a){ + return a && !d.isArray(a) && + (typeof NodeList != "undefined" && a.constructor === NodeList || a.constructor === this._NodeListCtor) ? + aps.call(a, 0) : a; + }); + return this._wrap(apc.apply(t, m), this); // dojo.NodeList + }, + + map: function(/*Function*/ func, /*Function?*/ obj){ + // summary: + // see dojo.map(). The primary difference is that the acted-on + // array is implicitly this NodeList and the return is a + // dojo.NodeList (a subclass of Array) + ///return d.map(this, func, obj, d.NodeList); // dojo.NodeList + return this._wrap(d.map(this, func, obj), this); // dojo.NodeList + }, + + forEach: function(callback, thisObj){ + // summary: + // see `dojo.forEach()`. The primary difference is that the acted-on + // array is implicitly this NodeList. If you want the option to break out + // of the forEach loop, use every() or some() instead. + d.forEach(this, callback, thisObj); + // non-standard return to allow easier chaining + return this; // dojo.NodeList + }, + + /*===== + coords: function(){ + // summary: + // Returns the box objects of all elements in a node list as + // an Array (*not* a NodeList). Acts like `dojo.coords`, though assumes + // the node passed is each node in this list. + + return d.map(this, d.coords); // Array + }, + + position: function(){ + // summary: + // Returns border-box objects (x/y/w/h) of all elements in a node list + // as an Array (*not* a NodeList). Acts like `dojo.position`, though + // assumes the node passed is each node in this list. + + return d.map(this, d.position); // Array + }, + + attr: function(property, value){ + // summary: + // gets or sets the DOM attribute for every element in the + // NodeList. See also `dojo.attr` + // property: String + // the attribute to get/set + // value: String? + // optional. The value to set the property to + // returns: + // if no value is passed, the result is an array of attribute values + // If a value is passed, the return is this NodeList + // example: + // Make all nodes with a particular class focusable: + // | dojo.query(".focusable").attr("tabIndex", -1); + // example: + // Disable a group of buttons: + // | dojo.query("button.group").attr("disabled", true); + // example: + // innerHTML can be assigned or retreived as well: + // | // get the innerHTML (as an array) for each list item + // | var ih = dojo.query("li.replaceable").attr("innerHTML"); + return; // dojo.NodeList + return; // Array + }, + + style: function(property, value){ + // summary: + // gets or sets the CSS property for every element in the NodeList + // property: String + // the CSS property to get/set, in JavaScript notation + // ("lineHieght" instead of "line-height") + // value: String? + // optional. The value to set the property to + // returns: + // if no value is passed, the result is an array of strings. + // If a value is passed, the return is this NodeList + return; // dojo.NodeList + return; // Array + }, + + addClass: function(className){ + // summary: + // adds the specified class to every node in the list + // className: String|Array + // A String class name to add, or several space-separated class names, + // or an array of class names. + return; // dojo.NodeList + }, + + removeClass: function(className){ + // summary: + // removes the specified class from every node in the list + // className: String|Array? + // An optional String class name to remove, or several space-separated + // class names, or an array of class names. If omitted, all class names + // will be deleted. + // returns: + // dojo.NodeList, this list + return; // dojo.NodeList + }, + + toggleClass: function(className, condition){ + // summary: + // Adds a class to node if not present, or removes if present. + // Pass a boolean condition if you want to explicitly add or remove. + // condition: Boolean? + // If passed, true means to add the class, false means to remove. + // className: String + // the CSS class to add + return; // dojo.NodeList + }, + + connect: function(methodName, objOrFunc, funcName){ + // summary: + // attach event handlers to every item of the NodeList. Uses dojo.connect() + // so event properties are normalized + // methodName: String + // the name of the method to attach to. For DOM events, this should be + // the lower-case name of the event + // objOrFunc: Object|Function|String + // if 2 arguments are passed (methodName, objOrFunc), objOrFunc should + // reference a function or be the name of the function in the global + // namespace to attach. If 3 arguments are provided + // (methodName, objOrFunc, funcName), objOrFunc must be the scope to + // locate the bound function in + // funcName: String? + // optional. A string naming the function in objOrFunc to bind to the + // event. May also be a function reference. + // example: + // add an onclick handler to every button on the page + // | dojo.query("div:nth-child(odd)").connect("onclick", function(e){ + // | console.log("clicked!"); + // | }); + // example: + // attach foo.bar() to every odd div's onmouseover + // | dojo.query("div:nth-child(odd)").connect("onmouseover", foo, "bar"); + }, + + empty: function(){ + // summary: + // clears all content from each node in the list. Effectively + // equivalent to removing all child nodes from every item in + // the list. + return this.forEach("item.innerHTML='';"); // dojo.NodeList + // FIXME: should we be checking for and/or disposing of widgets below these nodes? + }, + =====*/ + + // useful html methods + coords: adaptAsMap(d.coords), + position: adaptAsMap(d.position), + + // FIXME: connectPublisher()? connectRunOnce()? + + /* + destroy: function(){ + // summary: + // destroys every item in the list. + this.forEach(d.destroy); + // FIXME: should we be checking for and/or disposing of widgets below these nodes? + }, + */ + + place: function(/*String||Node*/ queryOrNode, /*String*/ position){ + // summary: + // places elements of this node list relative to the first element matched + // by queryOrNode. Returns the original NodeList. See: `dojo.place` + // queryOrNode: + // may be a string representing any valid CSS3 selector or a DOM node. + // In the selector case, only the first matching element will be used + // for relative positioning. + // position: + // can be one of: + // | "last" (default) + // | "first" + // | "before" + // | "after" + // | "only" + // | "replace" + // or an offset in the childNodes property + var item = d.query(queryOrNode)[0]; + return this.forEach(function(node){ d.place(node, item, position); }); // dojo.NodeList + }, + + orphan: function(/*String?*/ simpleFilter){ + // summary: + // removes elements in this list that match the simple filter + // from their parents and returns them as a new NodeList. + // simpleFilter: + // single-expression CSS rule. For example, ".thinger" or + // "#someId[attrName='value']" but not "div > span". In short, + // anything which does not invoke a descent to evaluate but + // can instead be used to test a single node is acceptable. + // returns: + // `dojo.NodeList` containing the orpahned elements + return (simpleFilter ? d._filterQueryResult(this, simpleFilter) : this).forEach(orphan); // dojo.NodeList + }, + + adopt: function(/*String||Array||DomNode*/ queryOrListOrNode, /*String?*/ position){ + // summary: + // places any/all elements in queryOrListOrNode at a + // position relative to the first element in this list. + // Returns a dojo.NodeList of the adopted elements. + // queryOrListOrNode: + // a DOM node or a query string or a query result. + // Represents the nodes to be adopted relative to the + // first element of this NodeList. + // position: + // can be one of: + // | "last" (default) + // | "first" + // | "before" + // | "after" + // | "only" + // | "replace" + // or an offset in the childNodes property + return d.query(queryOrListOrNode).place(this[0], position)._stash(this); // dojo.NodeList + }, + + // FIXME: do we need this? + query: function(/*String*/ queryStr){ + // summary: + // Returns a new list whose memebers match the passed query, + // assuming elements of the current NodeList as the root for + // each search. + // example: + // assume a DOM created by this markup: + // |
    + // |

    + // | bacon is tasty, dontcha think? + // |

    + // |
    + // |
    + // |

    great commedians may not be funny in person

    + // |
    + // If we are presented with the following defintion for a NodeList: + // | var l = new dojo.NodeList(dojo.byId("foo"), dojo.byId("bar")); + // it's possible to find all span elements under paragraphs + // contained by these elements with this sub-query: + // | var spans = l.query("p span"); + + // FIXME: probably slow + if(!queryStr){ return this; } + var ret = this.map(function(node){ + // FIXME: why would we ever get undefined here? + return d.query(queryStr, node).filter(function(subNode){ return subNode !== undefined; }); + }); + return this._wrap(apc.apply([], ret), this); // dojo.NodeList + }, + + filter: function(/*String|Function*/ simpleFilter){ + // summary: + // "masks" the built-in javascript filter() method (supported + // in Dojo via `dojo.filter`) to support passing a simple + // string filter in addition to supporting filtering function + // objects. + // simpleFilter: + // If a string, a single-expression CSS rule. For example, + // ".thinger" or "#someId[attrName='value']" but not "div > + // span". In short, anything which does not invoke a descent + // to evaluate but can instead be used to test a single node + // is acceptable. + // example: + // "regular" JS filter syntax as exposed in dojo.filter: + // | dojo.query("*").filter(function(item){ + // | // highlight every paragraph + // | return (item.nodeName == "p"); + // | }).style("backgroundColor", "yellow"); + // example: + // the same filtering using a CSS selector + // | dojo.query("*").filter("p").styles("backgroundColor", "yellow"); + + var a = arguments, items = this, start = 0; + if(typeof simpleFilter == "string"){ // inline'd type check + items = d._filterQueryResult(this, a[0]); + if(a.length == 1){ + // if we only got a string query, pass back the filtered results + return items._stash(this); // dojo.NodeList + } + // if we got a callback, run it over the filtered items + start = 1; + } + return this._wrap(d.filter(items, a[start], a[start + 1]), this); // dojo.NodeList + }, + + /* + // FIXME: should this be "copyTo" and include parenting info? + clone: function(){ + // summary: + // creates node clones of each element of this list + // and returns a new list containing the clones + }, + */ + + addContent: function(/*String||DomNode||Object||dojo.NodeList*/ content, /*String||Integer?*/ position){ + // summary: + // add a node, NodeList or some HTML as a string to every item in the + // list. Returns the original list. + // description: + // a copy of the HTML content is added to each item in the + // list, with an optional position argument. If no position + // argument is provided, the content is appended to the end of + // each item. + // content: + // DOM node, HTML in string format, a NodeList or an Object. If a DOM node or + // NodeList, the content will be cloned if the current NodeList has more than one + // element. Only the DOM nodes are cloned, no event handlers. If it is an Object, + // it should be an object with at "template" String property that has the HTML string + // to insert. If dojo.string has already been dojo.required, then dojo.string.substitute + // will be used on the "template" to generate the final HTML string. Other allowed + // properties on the object are: "parse" if the HTML + // string should be parsed for widgets (dojo.require("dojo.parser") to get that + // option to work), and "templateFunc" if a template function besides dojo.string.substitute + // should be used to transform the "template". + // position: + // can be one of: + // | "last"||"end" (default) + // | "first||"start" + // | "before" + // | "after" + // | "replace" (replaces nodes in this NodeList with new content) + // | "only" (removes other children of the nodes so new content is hte only child) + // or an offset in the childNodes property + // example: + // appends content to the end if the position is ommitted + // | dojo.query("h3 > p").addContent("hey there!"); + // example: + // add something to the front of each element that has a + // "thinger" property: + // | dojo.query("[thinger]").addContent("...", "first"); + // example: + // adds a header before each element of the list + // | dojo.query(".note").addContent("

    NOTE:

    ", "before"); + // example: + // add a clone of a DOM node to the end of every element in + // the list, removing it from its existing parent. + // | dojo.query(".note").addContent(dojo.byId("foo")); + // example: + // Append nodes from a templatized string. + // dojo.require("dojo.string"); + // dojo.query(".note").addContent({ + // template: '${id}: ${name}', + // id: "user332", + // name: "Mr. Anderson" + // }); + // example: + // Append nodes from a templatized string that also has widgets parsed. + // dojo.require("dojo.string"); + // dojo.require("dojo.parser"); + // var notes = dojo.query(".note").addContent({ + // template: '', + // parse: true, + // text: "Send" + // }); + content = this._normalize(content, this[0]); + for(var i = 0, node; node = this[i]; i++){ + this._place(content, node, position, i > 0); + } + return this; //dojo.NodeList + }, + + instantiate: function(/*String|Object*/ declaredClass, /*Object?*/ properties){ + // summary: + // Create a new instance of a specified class, using the + // specified properties and each node in the nodeList as a + // srcNodeRef. + // example: + // Grabs all buttons in the page and converts them to diji.form.Buttons. + // | var buttons = dojo.query("button").instantiate("dijit.form.Button", {showLabel: true}); + var c = d.isFunction(declaredClass) ? declaredClass : d.getObject(declaredClass); + properties = properties || {}; + return this.forEach(function(node){ + new c(properties, node); + }); // dojo.NodeList + }, + + at: function(/*===== index =====*/){ + // summary: + // Returns a new NodeList comprised of items in this NodeList + // at the given index or indices. + // + // index: Integer... + // One or more 0-based indices of items in the current + // NodeList. A negative index will start at the end of the + // list and go backwards. + // + // example: + // Shorten the list to the first, second, and third elements + // | dojo.query("a").at(0, 1, 2).forEach(fn); + // + // example: + // Retrieve the first and last elements of a unordered list: + // | dojo.query("ul > li").at(0, -1).forEach(cb); + // + // example: + // Do something for the first element only, but end() out back to + // the original list and continue chaining: + // | dojo.query("a").at(0).onclick(fn).end().forEach(function(n){ + // | console.log(n); // all anchors on the page. + // | }) + // + // returns: + // dojo.NodeList + var t = new this._NodeListCtor(); + d.forEach(arguments, function(i){ + if(i < 0){ i = this.length + i } + if(this[i]){ t.push(this[i]); } + }, this); + return t._stash(this); // dojo.NodeList + } + + }); + + nl.events = [ + // summary: list of all DOM events used in NodeList + "blur", "focus", "change", "click", "error", "keydown", "keypress", + "keyup", "load", "mousedown", "mouseenter", "mouseleave", "mousemove", + "mouseout", "mouseover", "mouseup", "submit" + ]; + + // FIXME: pseudo-doc the above automatically generated on-event functions + + // syntactic sugar for DOM events + d.forEach(nl.events, function(evt){ + var _oe = "on" + evt; + nlp[_oe] = function(a, b){ + return this.connect(_oe, a, b); + } + // FIXME: should these events trigger publishes? + /* + return (a ? this.connect(_oe, a, b) : + this.forEach(function(n){ + // FIXME: + // listeners get buried by + // addEventListener and can't be dug back + // out to be triggered externally. + // see: + // http://developer.mozilla.org/en/docs/DOM:element + + console.log(n, evt, _oe); + + // FIXME: need synthetic event support! + var _e = { target: n, faux: true, type: evt }; + // dojo._event_listener._synthesizeEvent({}, { target: n, faux: true, type: evt }); + try{ n[evt](_e); }catch(e){ console.log(e); } + try{ n[_oe](_e); }catch(e){ console.log(e); } + }) + ); + */ + } + ); + })(); + } diff --git a/lib/dojo/_base/_loader/bootstrap.js b/lib/dojo/_base/_loader/bootstrap.js index 7cc168e5..3ef3012a 100644 --- a/lib/dojo/_base/_loader/bootstrap.js +++ b/lib/dojo/_base/_loader/bootstrap.js @@ -5,116 +5,500 @@ */ -(function(){ -if(typeof this["loadFirebugConsole"]=="function"){ -this["loadFirebugConsole"](); -}else{ -this.console=this.console||{}; -var cn=["assert","count","debug","dir","dirxml","error","group","groupEnd","info","profile","profileEnd","time","timeEnd","trace","warn","log"]; -var i=0,tn; -while((tn=cn[i++])){ -if(!console[tn]){ -(function(){ -var _1=tn+""; -console[_1]=("log" in console)?function(){ -var a=Array.apply({},arguments); -a.unshift(_1+":"); -console["log"](a.join(" ")); -}:function(){ -}; -console[_1]._fake=true; -})(); -} -} -} -if(typeof dojo=="undefined"){ -dojo={_scopeName:"dojo",_scopePrefix:"",_scopePrefixArgs:"",_scopeSuffix:"",_scopeMap:{},_scopeMapRev:{}}; -} -var d=dojo; -if(typeof dijit=="undefined"){ -dijit={_scopeName:"dijit"}; -} -if(typeof dojox=="undefined"){ -dojox={_scopeName:"dojox"}; -} -if(!d._scopeArgs){ -d._scopeArgs=[dojo,dijit,dojox]; -} -d.global=this; -d.config={isDebug:false,debugAtAllCosts:false}; -if(typeof djConfig!="undefined"){ -for(var _2 in djConfig){ -d.config[_2]=djConfig[_2]; -} -} -dojo.locale=d.config.locale; -var _3="$Rev: 22487 $".match(/\d+/); -dojo.version={major:1,minor:5,patch:0,flag:"",revision:_3?+_3[0]:NaN,toString:function(){ -with(d.version){ -return major+"."+minor+"."+patch+flag+" ("+revision+")"; -} -}}; -if(typeof OpenAjax!="undefined"){ -OpenAjax.hub.registerLibrary(dojo._scopeName,"http://dojotoolkit.org",d.version.toString()); -} -var _4,_5,_6={}; -for(var i in {toString:1}){ -_4=[]; -break; -} -dojo._extraNames=_4=_4||["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"]; -_5=_4.length; -dojo._mixin=function(_7,_8){ -var _9,s,i; -for(_9 in _8){ -s=_8[_9]; -if(!(_9 in _7)||(_7[_9]!==s&&(!(_9 in _6)||_6[_9]!==s))){ -_7[_9]=s; -} -} -if(_5&&_8){ -for(i=0;i<_5;++i){ -_9=_4[i]; -s=_8[_9]; -if(!(_9 in _7)||(_7[_9]!==s&&(!(_9 in _6)||_6[_9]!==s))){ -_7[_9]=s; -} -} -} -return _7; -}; -dojo.mixin=function(_a,_b){ -if(!_a){ -_a={}; -} -for(var i=1,l=arguments.length;i` tag inclusion. This may double-request resources and + // cause problems with scripts which expect `dojo.require()` to + // preform synchronously. `debugAtAllCosts` can be an invaluable + // debugging aid, but when using it, ensure that all code which + // depends on Dojo modules is wrapped in `dojo.addOnLoad()` handlers. + // Due to the somewhat unpredictable side-effects of using + // `debugAtAllCosts`, it is strongly recommended that you enable this + // flag as a last resort. `debugAtAllCosts` has no effect when loading + // resources across domains. For usage information, see the + // [Dojo Book](http://dojotoolkit.org/book/book-dojo/part-4-meta-dojo-making-your-dojo-code-run-faster-and-better/debugging-facilities/deb) + debugAtAllCosts: false, + // locale: String + // The locale to assume for loading localized resources in this page, + // specified according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt). + // Must be specified entirely in lowercase, e.g. `en-us` and `zh-cn`. + // See the documentation for `dojo.i18n` and `dojo.requireLocalization` + // for details on loading localized resources. If no locale is specified, + // Dojo assumes the locale of the user agent, according to `navigator.userLanguage` + // or `navigator.language` properties. + locale: undefined, + // extraLocale: Array + // No default value. Specifies additional locales whose + // resources should also be loaded alongside the default locale when + // calls to `dojo.requireLocalization()` are processed. + extraLocale: undefined, + // baseUrl: String + // The directory in which `dojo.js` is located. Under normal + // conditions, Dojo auto-detects the correct location from which it + // was loaded. You may need to manually configure `baseUrl` in cases + // where you have renamed `dojo.js` or in which `` tags confuse + // some browsers (e.g. IE 6). The variable `dojo.baseUrl` is assigned + // either the value of `djConfig.baseUrl` if one is provided or the + // auto-detected root if not. Other modules are located relative to + // this path. The path should end in a slash. + baseUrl: undefined, + // modulePaths: Object + // A map of module names to paths relative to `dojo.baseUrl`. The + // key/value pairs correspond directly to the arguments which + // `dojo.registerModulePath` accepts. Specifiying + // `djConfig.modulePaths = { "foo": "../../bar" }` is the equivalent + // of calling `dojo.registerModulePath("foo", "../../bar");`. Multiple + // modules may be configured via `djConfig.modulePaths`. + modulePaths: {}, + // afterOnLoad: Boolean + // Indicates Dojo was added to the page after the page load. In this case + // Dojo will not wait for the page DOMContentLoad/load events and fire + // its dojo.addOnLoad callbacks after making sure all outstanding + // dojo.required modules have loaded. Only works with a built dojo.js, + // it does not work the dojo.js directly from source control. + afterOnLoad: false, + // addOnLoad: Function or Array + // Adds a callback via dojo.addOnLoad. Useful when Dojo is added after + // the page loads and djConfig.afterOnLoad is true. Supports the same + // arguments as dojo.addOnLoad. When using a function reference, use + // `djConfig.addOnLoad = function(){};`. For object with function name use + // `djConfig.addOnLoad = [myObject, "functionName"];` and for object with + // function reference use + // `djConfig.addOnLoad = [myObject, function(){}];` + addOnLoad: null, + // require: Array + // An array of module names to be loaded immediately after dojo.js has been included + // in a page. + require: [], + // defaultDuration: Array + // Default duration, in milliseconds, for wipe and fade animations within dijits. + // Assigned to dijit.defaultDuration. + defaultDuration: 200, + // dojoBlankHtmlUrl: String + // Used by some modules to configure an empty iframe. Used by dojo.io.iframe and + // dojo.back, and dijit popup support in IE where an iframe is needed to make sure native + // controls do not bleed through the popups. Normally this configuration variable + // does not need to be set, except when using cross-domain/CDN Dojo builds. + // Save dojo/resources/blank.html to your domain and set `djConfig.dojoBlankHtmlUrl` + // to the path on your domain your copy of blank.html. + dojoBlankHtmlUrl: undefined, + // ioPublish: Boolean? + // Set this to true to enable publishing of topics for the different phases of + // IO operations. Publishing is done via dojo.publish. See dojo.__IoPublish for a list + // of topics that are published. + ioPublish: false, + // useCustomLogger: Anything? + // If set to a value that evaluates to true such as a string or array and + // isDebug is true and Firebug is not available or running, then it bypasses + // the creation of Firebug Lite allowing you to define your own console object. + useCustomLogger: undefined, + // transparentColor: Array + // Array containing the r, g, b components used as transparent color in dojo.Color; + // if undefined, [255,255,255] (white) will be used. + transparentColor: undefined, + // skipIeDomLoaded: Boolean + // For IE only, skip the DOMContentLoaded hack used. Sometimes it can cause an Operation + // Aborted error if the rest of the page triggers script defers before the DOM is ready. + // If this is config value is set to true, then dojo.addOnLoad callbacks will not be + // triggered until the page load event, which is after images and iframes load. If you + // want to trigger the callbacks sooner, you can put a script block in the bottom of + // your HTML that calls dojo._loadInit();. If you are using multiversion support, change + // "dojo." to the appropriate scope name for dojo. + skipIeDomLoaded: false } -_f=(p in _f?_f[p]:(_d?_f[p]={}:undefined)); +=====*/ + +(function(){ + // firebug stubs + + if(typeof this["loadFirebugConsole"] == "function"){ + // for Firebug 1.2 + this["loadFirebugConsole"](); + }else{ + this.console = this.console || {}; + + // Be careful to leave 'log' always at the end + var cn = [ + "assert", "count", "debug", "dir", "dirxml", "error", "group", + "groupEnd", "info", "profile", "profileEnd", "time", "timeEnd", + "trace", "warn", "log" + ]; + var i=0, tn; + while((tn=cn[i++])){ + if(!console[tn]){ + (function(){ + var tcn = tn+""; + console[tcn] = ('log' in console) ? function(){ + var a = Array.apply({}, arguments); + a.unshift(tcn+":"); + console["log"](a.join(" ")); + } : function(){} + console[tcn]._fake = true; + })(); + } + } + } + + //TODOC: HOW TO DOC THIS? + // dojo is the root variable of (almost all) our public symbols -- make sure it is defined. + if(typeof dojo == "undefined"){ + dojo = { + _scopeName: "dojo", + _scopePrefix: "", + _scopePrefixArgs: "", + _scopeSuffix: "", + _scopeMap: {}, + _scopeMapRev: {} + }; + } + + var d = dojo; + + //Need placeholders for dijit and dojox for scoping code. + if(typeof dijit == "undefined"){ + dijit = {_scopeName: "dijit"}; + } + if(typeof dojox == "undefined"){ + dojox = {_scopeName: "dojox"}; + } + + if(!d._scopeArgs){ + d._scopeArgs = [dojo, dijit, dojox]; + } + +/*===== +dojo.global = { + // summary: + // Alias for the global scope + // (e.g. the window object in a browser). + // description: + // Refer to 'dojo.global' rather than referring to window to ensure your + // code runs correctly in contexts other than web browsers (e.g. Rhino on a server). } -return _f; -}; -dojo.setObject=function(_10,_11,_12){ -var _13=_10.split("."),p=_13.pop(),obj=d._getProp(_13,true,_12); -return obj&&p?(obj[p]=_11):undefined; -}; -dojo.getObject=function(_14,_15,_16){ -return d._getProp(_14.split("."),_15,_16); -}; -dojo.exists=function(_17,obj){ -return !!d.getObject(_17,false,obj); -}; -dojo["eval"]=function(_18){ -return d.global.eval?d.global.eval(_18):eval(_18); -}; -d.deprecated=d.experimental=function(){ -}; +=====*/ + d.global = this; + + d.config =/*===== djConfig = =====*/{ + isDebug: false, + debugAtAllCosts: false + }; + + if(typeof djConfig != "undefined"){ + for(var opt in djConfig){ + d.config[opt] = djConfig[opt]; + } + } + +/*===== + // Override locale setting, if specified + dojo.locale = { + // summary: the locale as defined by Dojo (read-only) + }; +=====*/ + dojo.locale = d.config.locale; + + var rev = "$Rev: 22487 $".match(/\d+/); + +/*===== + dojo.version = function(){ + // summary: + // Version number of the Dojo Toolkit + // major: Integer + // Major version. If total version is "1.2.0beta1", will be 1 + // minor: Integer + // Minor version. If total version is "1.2.0beta1", will be 2 + // patch: Integer + // Patch version. If total version is "1.2.0beta1", will be 0 + // flag: String + // Descriptor flag. If total version is "1.2.0beta1", will be "beta1" + // revision: Number + // The SVN rev from which dojo was pulled + this.major = 0; + this.minor = 0; + this.patch = 0; + this.flag = ""; + this.revision = 0; + } +=====*/ + dojo.version = { + major: 1, minor: 5, patch: 0, flag: "", + revision: rev ? +rev[0] : NaN, + toString: function(){ + with(d.version){ + return major + "." + minor + "." + patch + flag + " (" + revision + ")"; // String + } + } + } + + // Register with the OpenAjax hub + if(typeof OpenAjax != "undefined"){ + OpenAjax.hub.registerLibrary(dojo._scopeName, "http://dojotoolkit.org", d.version.toString()); + } + + var extraNames, extraLen, empty = {}; + for(var i in {toString: 1}){ extraNames = []; break; } + dojo._extraNames = extraNames = extraNames || ["hasOwnProperty", "valueOf", "isPrototypeOf", + "propertyIsEnumerable", "toLocaleString", "toString", "constructor"]; + extraLen = extraNames.length; + + dojo._mixin = function(/*Object*/ target, /*Object*/ source){ + // summary: + // Adds all properties and methods of source to target. This addition + // is "prototype extension safe", so that instances of objects + // will not pass along prototype defaults. + var name, s, i; + for(name in source){ + // the "tobj" condition avoid copying properties in "source" + // inherited from Object.prototype. For example, if target has a custom + // toString() method, don't overwrite it with the toString() method + // that source inherited from Object.prototype + s = source[name]; + if(!(name in target) || (target[name] !== s && (!(name in empty) || empty[name] !== s))){ + target[name] = s; + } + } + // IE doesn't recognize some custom functions in for..in + if(extraLen && source){ + for(i = 0; i < extraLen; ++i){ + name = extraNames[i]; + s = source[name]; + if(!(name in target) || (target[name] !== s && (!(name in empty) || empty[name] !== s))){ + target[name] = s; + } + } + } + return target; // Object + } + + dojo.mixin = function(/*Object*/obj, /*Object...*/props){ + // summary: + // Adds all properties and methods of props to obj and returns the + // (now modified) obj. + // description: + // `dojo.mixin` can mix multiple source objects into a + // destination object which is then returned. Unlike regular + // `for...in` iteration, `dojo.mixin` is also smart about avoiding + // extensions which other toolkits may unwisely add to the root + // object prototype + // obj: + // The object to mix properties into. Also the return value. + // props: + // One or more objects whose values are successively copied into + // obj. If more than one of these objects contain the same value, + // the one specified last in the function call will "win". + // example: + // make a shallow copy of an object + // | var copy = dojo.mixin({}, source); + // example: + // many class constructors often take an object which specifies + // values to be configured on the object. In this case, it is + // often simplest to call `dojo.mixin` on the `this` object: + // | dojo.declare("acme.Base", null, { + // | constructor: function(properties){ + // | // property configuration: + // | dojo.mixin(this, properties); + // | + // | console.log(this.quip); + // | // ... + // | }, + // | quip: "I wasn't born yesterday, you know - I've seen movies.", + // | // ... + // | }); + // | + // | // create an instance of the class and configure it + // | var b = new acme.Base({quip: "That's what it does!" }); + // example: + // copy in properties from multiple objects + // | var flattened = dojo.mixin( + // | { + // | name: "Frylock", + // | braces: true + // | }, + // | { + // | name: "Carl Brutanananadilewski" + // | } + // | ); + // | + // | // will print "Carl Brutanananadilewski" + // | console.log(flattened.name); + // | // will print "true" + // | console.log(flattened.braces); + if(!obj){ obj = {}; } + for(var i=1, l=arguments.length; i=0){ -d.isOpera=tv; -} -if(_6.indexOf("AdobeAIR")>=0){ -d.isAIR=1; -} -d.isKhtml=(_7.indexOf("Konqueror")>=0)?tv:0; -d.isWebKit=parseFloat(_6.split("WebKit/")[1])||undefined; -d.isChrome=parseFloat(_6.split("Chrome/")[1])||undefined; -d.isMac=_7.indexOf("Macintosh")>=0; -var _8=Math.max(_7.indexOf("WebKit"),_7.indexOf("Safari"),0); -if(_8&&!dojo.isChrome){ -d.isSafari=parseFloat(_7.split("Version/")[1]); -if(!d.isSafari||parseFloat(_7.substr(_8+7))<=419.3){ -d.isSafari=2; -} -} -if(_6.indexOf("Gecko")>=0&&!d.isKhtml&&!d.isWebKit){ -d.isMozilla=d.isMoz=tv; -} -if(d.isMoz){ -d.isFF=parseFloat(_6.split("Firefox/")[1]||_6.split("Minefield/")[1])||undefined; -} -if(document.all&&!d.isOpera){ -d.isIE=parseFloat(_7.split("MSIE ")[1])||undefined; -var _9=document.documentMode; -if(_9&&_9!=5&&Math.floor(d.isIE)!=_9){ -d.isIE=_9; -} -} -if(dojo.isIE&&window.location.protocol==="file:"){ -dojo.config.ieForceActiveXXhr=true; -} -d.isQuirks=document.compatMode=="BackCompat"; -d.locale=dojo.config.locale||(d.isIE?n.userLanguage:n.language).toLowerCase(); -d._XMLHTTP_PROGIDS=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"]; -d._xhrObj=function(){ -var _a,_b; -if(!dojo.isIE||!dojo.config.ieForceActiveXXhr){ -try{ -_a=new XMLHttpRequest(); -} -catch(e){ -} -} -if(!_a){ -for(var i=0;i<3;++i){ -var _c=d._XMLHTTP_PROGIDS[i]; -try{ -_a=new ActiveXObject(_c); -} -catch(e){ -_b=e; -} -if(_a){ -d._XMLHTTP_PROGIDS=[_c]; -break; -} -} -} -if(!_a){ -throw new Error("XMLHTTP not available: "+_b); -} -return _a; -}; -d._isDocumentOk=function(_d){ -var _e=_d.status||0,lp=location.protocol; -return (_e>=200&&_e<300)||_e==304||_e==1223||(!_e&&(lp=="file:"||lp=="chrome:"||lp=="chrome-extension:"||lp=="app:")); +/*===== +dojo.isBrowser = { + // example: + // | if(dojo.isBrowser){ ... } }; -var _f=window.location+""; -var _10=document.getElementsByTagName("base"); -var _11=(_10&&_10.length>0); -d._getText=function(uri,_12){ -var _13=d._xhrObj(); -if(!_11&&dojo._Url){ -uri=(new dojo._Url(_f,uri)).toString(); -} -if(d.config.cacheBust){ -uri+=""; -uri+=(uri.indexOf("?")==-1?"?":"&")+String(d.config.cacheBust).replace(/\W+/g,""); -} -_13.open("GET",uri,false); -try{ -_13.send(null); -if(!d._isDocumentOk(_13)){ -var err=Error("Unable to load "+uri+" status:"+_13.status); -err.status=_13.status; -err.responseText=_13.responseText; -throw err; -} -} -catch(e){ -if(_12){ -return null; -} -throw e; -} -return _13.responseText; -}; -var _14=window; -var _15=function(_16,fp){ -var _17=_14.attachEvent||_14.addEventListener; -_16=_14.attachEvent?_16:_16.substring(2); -_17(_16,function(){ -fp.apply(_14,arguments); -},false); -}; -d._windowUnloaders=[]; -d.windowUnloaded=function(){ -var mll=d._windowUnloaders; -while(mll.length){ -(mll.pop())(); -} -d=null; -}; -var _18=0; -d.addOnWindowUnload=function(obj,_19){ -d._onto(d._windowUnloaders,obj,_19); -if(!_18){ -_18=1; -_15("onunload",d.windowUnloaded); -} + +dojo.isFF = { + // example: + // | if(dojo.isFF > 1){ ... } }; -var _1a=0; -d.addOnUnload=function(obj,_1b){ -d._onto(d._unloaders,obj,_1b); -if(!_1a){ -_1a=1; -_15("onbeforeunload",dojo.unloaded); -} + +dojo.isIE = { + // example: + // | if(dojo.isIE > 6){ + // | // we are IE7 + // | } }; -})(); -dojo._initFired=false; -dojo._loadInit=function(e){ -if(dojo._scrollIntervalId){ -clearInterval(dojo._scrollIntervalId); -dojo._scrollIntervalId=0; -} -if(!dojo._initFired){ -dojo._initFired=true; -if(!dojo.config.afterOnLoad&&window.detachEvent){ -window.detachEvent("onload",dojo._loadInit); -} -if(dojo._inFlightCount==0){ -dojo._modulesLoaded(); -} -} + +dojo.isSafari = { + // example: + // | if(dojo.isSafari){ ... } + // example: + // Detect iPhone: + // | if(dojo.isSafari && navigator.userAgent.indexOf("iPhone") != -1){ + // | // we are iPhone. Note, iPod touch reports "iPod" above and fails this test. + // | } }; -if(!dojo.config.afterOnLoad){ -if(document.addEventListener){ -document.addEventListener("DOMContentLoaded",dojo._loadInit,false); -window.addEventListener("load",dojo._loadInit,false); -}else{ -if(window.attachEvent){ -window.attachEvent("onload",dojo._loadInit); -if(!dojo.config.skipIeDomLoaded&&self===self.top){ -dojo._scrollIntervalId=setInterval(function(){ -try{ -if(document.body){ -document.documentElement.doScroll("left"); -dojo._loadInit(); -} -} -catch(e){ -} -},30); -} -} -} -} -if(dojo.isIE){ -try{ -(function(){ -document.namespaces.add("v","urn:schemas-microsoft-com:vml"); -var _1c=["*","group","roundrect","oval","shape","rect","imagedata","path","textpath","text"],i=0,l=1,s=document.createStyleSheet(); -if(dojo.isIE>=8){ -i=1; -l=_1c.length; -} -for(;i= 0){ d.isOpera = tv; } + if(dua.indexOf("AdobeAIR") >= 0){ d.isAIR = 1; } + d.isKhtml = (dav.indexOf("Konqueror") >= 0) ? tv : 0; + d.isWebKit = parseFloat(dua.split("WebKit/")[1]) || undefined; + d.isChrome = parseFloat(dua.split("Chrome/")[1]) || undefined; + d.isMac = dav.indexOf("Macintosh") >= 0; + + // safari detection derived from: + // http://developer.apple.com/internet/safari/faq.html#anchor2 + // http://developer.apple.com/internet/safari/uamatrix.html + var index = Math.max(dav.indexOf("WebKit"), dav.indexOf("Safari"), 0); + if(index && !dojo.isChrome){ + // try to grab the explicit Safari version first. If we don't get + // one, look for less than 419.3 as the indication that we're on something + // "Safari 2-ish". + d.isSafari = parseFloat(dav.split("Version/")[1]); + if(!d.isSafari || parseFloat(dav.substr(index + 7)) <= 419.3){ + d.isSafari = 2; + } + } + + if(dua.indexOf("Gecko") >= 0 && !d.isKhtml && !d.isWebKit){ d.isMozilla = d.isMoz = tv; } + if(d.isMoz){ + //We really need to get away from this. Consider a sane isGecko approach for the future. + d.isFF = parseFloat(dua.split("Firefox/")[1] || dua.split("Minefield/")[1]) || undefined; + } + if(document.all && !d.isOpera){ + d.isIE = parseFloat(dav.split("MSIE ")[1]) || undefined; + //In cases where the page has an HTTP header or META tag with + //X-UA-Compatible, then it is in emulation mode. + //Make sure isIE reflects the desired version. + //document.documentMode of 5 means quirks mode. + //Only switch the value if documentMode's major version + //is different from isIE's major version. + var mode = document.documentMode; + if(mode && mode != 5 && Math.floor(d.isIE) != mode){ + d.isIE = mode; + } + } + + //Workaround to get local file loads of dojo to work on IE 7 + //by forcing to not use native xhr. + if(dojo.isIE && window.location.protocol === "file:"){ + dojo.config.ieForceActiveXXhr=true; + } + + d.isQuirks = document.compatMode == "BackCompat"; + + // TODO: is the HTML LANG attribute relevant? + d.locale = dojo.config.locale || (d.isIE ? n.userLanguage : n.language).toLowerCase(); + + // These are in order of decreasing likelihood; this will change in time. + d._XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0']; + + d._xhrObj = function(){ + // summary: + // does the work of portably generating a new XMLHTTPRequest object. + var http, last_e; + if(!dojo.isIE || !dojo.config.ieForceActiveXXhr){ + try{ http = new XMLHttpRequest(); }catch(e){} + } + if(!http){ + for(var i=0; i<3; ++i){ + var progid = d._XMLHTTP_PROGIDS[i]; + try{ + http = new ActiveXObject(progid); + }catch(e){ + last_e = e; + } + + if(http){ + d._XMLHTTP_PROGIDS = [progid]; // so faster next time + break; + } + } + } + + if(!http){ + throw new Error("XMLHTTP not available: "+last_e); + } + + return http; // XMLHTTPRequest instance + } + + d._isDocumentOk = function(http){ + var stat = http.status || 0, + lp = location.protocol; + return (stat >= 200 && stat < 300) || // Boolean + stat == 304 || // allow any 2XX response code + stat == 1223 || // get it out of the cache + // Internet Explorer mangled the status code OR we're Titanium/browser chrome/chrome extension requesting a local file + (!stat && (lp == "file:" || lp == "chrome:" || lp == "chrome-extension:" || lp == "app:") ); + } + + //See if base tag is in use. + //This is to fix http://trac.dojotoolkit.org/ticket/3973, + //but really, we need to find out how to get rid of the dojo._Url reference + //below and still have DOH work with the dojo.i18n test following some other + //test that uses the test frame to load a document (trac #2757). + //Opera still has problems, but perhaps a larger issue of base tag support + //with XHR requests (hasBase is true, but the request is still made to document + //path, not base path). + var owloc = window.location+""; + var base = document.getElementsByTagName("base"); + var hasBase = (base && base.length > 0); + + d._getText = function(/*URI*/ uri, /*Boolean*/ fail_ok){ + // summary: Read the contents of the specified uri and return those contents. + // uri: + // A relative or absolute uri. If absolute, it still must be in + // the same "domain" as we are. + // fail_ok: + // Default false. If fail_ok and loading fails, return null + // instead of throwing. + // returns: The response text. null is returned when there is a + // failure and failure is okay (an exception otherwise) + + // NOTE: must be declared before scope switches ie. this._xhrObj() + var http = d._xhrObj(); + + if(!hasBase && dojo._Url){ + uri = (new dojo._Url(owloc, uri)).toString(); + } + + if(d.config.cacheBust){ + //Make sure we have a string before string methods are used on uri + uri += ""; + uri += (uri.indexOf("?") == -1 ? "?" : "&") + String(d.config.cacheBust).replace(/\W+/g,""); + } + + http.open('GET', uri, false); + try{ + http.send(null); + if(!d._isDocumentOk(http)){ + var err = Error("Unable to load "+uri+" status:"+ http.status); + err.status = http.status; + err.responseText = http.responseText; + throw err; + } + }catch(e){ + if(fail_ok){ return null; } // null + // rethrow the exception + throw e; + } + return http.responseText; // String + } + + + var _w = window; + var _handleNodeEvent = function(/*String*/evtName, /*Function*/fp){ + // summary: + // non-destructively adds the specified function to the node's + // evtName handler. + // evtName: should be in the form "onclick" for "onclick" handlers. + // Make sure you pass in the "on" part. + var _a = _w.attachEvent || _w.addEventListener; + evtName = _w.attachEvent ? evtName : evtName.substring(2); + _a(evtName, function(){ + fp.apply(_w, arguments); + }, false); + }; + + + d._windowUnloaders = []; + + d.windowUnloaded = function(){ + // summary: + // signal fired by impending window destruction. You may use + // dojo.addOnWindowUnload() to register a listener for this + // event. NOTE: if you wish to dojo.connect() to this method + // to perform page/application cleanup, be aware that this + // event WILL NOT fire if no handler has been registered with + // dojo.addOnWindowUnload. This behavior started in Dojo 1.3. + // Previous versions always triggered dojo.windowUnloaded. See + // dojo.addOnWindowUnload for more info. + var mll = d._windowUnloaders; + while(mll.length){ + (mll.pop())(); + } + d = null; + }; + + var _onWindowUnloadAttached = 0; + d.addOnWindowUnload = function(/*Object?|Function?*/obj, /*String|Function?*/functionName){ + // summary: + // registers a function to be triggered when window.onunload + // fires. + // description: + // The first time that addOnWindowUnload is called Dojo + // will register a page listener to trigger your unload + // handler with. Note that registering these handlers may + // destory "fastback" page caching in browsers that support + // it. Be careful trying to modify the DOM or access + // JavaScript properties during this phase of page unloading: + // they may not always be available. Consider + // dojo.addOnUnload() if you need to modify the DOM or do + // heavy JavaScript work since it fires at the eqivalent of + // the page's "onbeforeunload" event. + // example: + // | dojo.addOnWindowUnload(functionPointer) + // | dojo.addOnWindowUnload(object, "functionName"); + // | dojo.addOnWindowUnload(object, function(){ /* ... */}); + + d._onto(d._windowUnloaders, obj, functionName); + if(!_onWindowUnloadAttached){ + _onWindowUnloadAttached = 1; + _handleNodeEvent("onunload", d.windowUnloaded); + } + }; + + var _onUnloadAttached = 0; + d.addOnUnload = function(/*Object?|Function?*/obj, /*String|Function?*/functionName){ + // summary: + // registers a function to be triggered when the page unloads. + // description: + // The first time that addOnUnload is called Dojo will + // register a page listener to trigger your unload handler + // with. + // + // In a browser enviroment, the functions will be triggered + // during the window.onbeforeunload event. Be careful of doing + // too much work in an unload handler. onbeforeunload can be + // triggered if a link to download a file is clicked, or if + // the link is a javascript: link. In these cases, the + // onbeforeunload event fires, but the document is not + // actually destroyed. So be careful about doing destructive + // operations in a dojo.addOnUnload callback. + // + // Further note that calling dojo.addOnUnload will prevent + // browsers from using a "fast back" cache to make page + // loading via back button instantaneous. + // example: + // | dojo.addOnUnload(functionPointer) + // | dojo.addOnUnload(object, "functionName") + // | dojo.addOnUnload(object, function(){ /* ... */}); + + d._onto(d._unloaders, obj, functionName); + if(!_onUnloadAttached){ + _onUnloadAttached = 1; + _handleNodeEvent("onbeforeunload", dojo.unloaded); + } + }; + + })(); + + //START DOMContentLoaded + dojo._initFired = false; + dojo._loadInit = function(e){ + if(dojo._scrollIntervalId){ + clearInterval(dojo._scrollIntervalId); + dojo._scrollIntervalId = 0; + } + + if(!dojo._initFired){ + dojo._initFired = true; + + //Help out IE to avoid memory leak. + if(!dojo.config.afterOnLoad && window.detachEvent){ + window.detachEvent("onload", dojo._loadInit); + } + + if(dojo._inFlightCount == 0){ + dojo._modulesLoaded(); + } + } + } + + if(!dojo.config.afterOnLoad){ + if(document.addEventListener){ + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. It is OK if it does not, the fall through + //to window onload should be good enough. + document.addEventListener("DOMContentLoaded", dojo._loadInit, false); + window.addEventListener("load", dojo._loadInit, false); + }else if(window.attachEvent){ + window.attachEvent("onload", dojo._loadInit); + + //DOMContentLoaded approximation. Diego Perini found this MSDN article + //that indicates doScroll is available after DOM ready, so do a setTimeout + //to check when it is available. + //http://msdn.microsoft.com/en-us/library/ms531426.aspx + if(!dojo.config.skipIeDomLoaded && self === self.top){ + dojo._scrollIntervalId = setInterval(function (){ + try{ + //When dojo is loaded into an iframe in an IE HTML Application + //(HTA), such as in a selenium test, javascript in the iframe + //can't see anything outside of it, so self===self.top is true, + //but the iframe is not the top window and doScroll will be + //available before document.body is set. Test document.body + //before trying the doScroll trick + if(document.body){ + document.documentElement.doScroll("left"); + dojo._loadInit(); + } + }catch (e){} + }, 30); + } + } + } + + if(dojo.isIE){ + try{ + (function(){ + document.namespaces.add("v", "urn:schemas-microsoft-com:vml"); + var vmlElems = ["*", "group", "roundrect", "oval", "shape", "rect", "imagedata", "path", "textpath", "text"], + i = 0, l = 1, s = document.createStyleSheet(); + if(dojo.isIE >= 8){ + i = 1; + l = vmlElems.length; + } + for(; i < l; ++i){ + s.addRule("v\\:" + vmlElems[i], "behavior:url(#default#VML); display:inline-block"); + } + })(); + }catch(e){} + } + //END DOMContentLoaded + + + /* + OpenAjax.subscribe("OpenAjax", "onload", function(){ + if(dojo._inFlightCount == 0){ + dojo._modulesLoaded(); + } + }); + + OpenAjax.subscribe("OpenAjax", "onunload", function(){ + dojo.unloaded(); + }); + */ +} //if (typeof window != 'undefined') + +//Register any module paths set up in djConfig. Need to do this +//in the hostenvs since hostenv_browser can read djConfig from a +//script tag's attribute. (function(){ -var mp=dojo.config["modulePaths"]; -if(mp){ -for(var _1d in mp){ -dojo.registerModulePath(_1d,mp[_1d]); -} -} + var mp = dojo.config["modulePaths"]; + if(mp){ + for(var param in mp){ + dojo.registerModulePath(param, mp[param]); + } + } })(); + +//Load debug code if necessary. if(dojo.config.isDebug){ -dojo.require("dojo._firebug.firebug"); + dojo.require("dojo._firebug.firebug"); } + if(dojo.config.debugAtAllCosts){ -dojo.config.useXDomain=true; -dojo.require("dojo._base._loader.loader_xd"); -dojo.require("dojo._base._loader.loader_debug"); -dojo.require("dojo.i18n"); + dojo.config.useXDomain = true; + dojo.require("dojo._base._loader.loader_xd"); + dojo.require("dojo._base._loader.loader_debug"); + dojo.require("dojo.i18n"); } diff --git a/lib/dojo/_base/_loader/hostenv_ff_ext.js b/lib/dojo/_base/_loader/hostenv_ff_ext.js index 08242393..94a0a804 100644 --- a/lib/dojo/_base/_loader/hostenv_ff_ext.js +++ b/lib/dojo/_base/_loader/hostenv_ff_ext.js @@ -5,171 +5,334 @@ */ -if(typeof window!="undefined"){ -dojo.isBrowser=true; -dojo._name="browser"; -(function(){ -var d=dojo; -d.baseUrl=d.config.baseUrl; -var n=navigator; -var _1=n.userAgent; -var _2=n.appVersion; -var tv=parseFloat(_2); -d.isMozilla=d.isMoz=tv; -if(d.isMoz){ -d.isFF=parseFloat(_1.split("Firefox/")[1])||undefined; -} -d.isQuirks=document.compatMode=="BackCompat"; -d.locale=dojo.config.locale||n.language.toLowerCase(); -d._xhrObj=function(){ -return new XMLHttpRequest(); -}; -var _3=d._loadUri; -d._loadUri=function(_4,cb){ -var _5=["file:","chrome:","resource:"].some(function(_6){ -return String(_4).indexOf(_6)==0; -}); -if(_5){ -var l=Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader); -var _7=l.loadSubScript(_4,d.global); -if(cb){ -cb(_7); -} -return true; -}else{ -return _3.apply(d,arguments); -} -}; -d._isDocumentOk=function(_8){ -var _9=_8.status||0; -return (_9>=200&&_9<300)||_9==304||_9==1223||(!_9&&(location.protocol=="file:"||location.protocol=="chrome:")); -}; -var _a=false; -d._getText=function(_b,_c){ -var _d=d._xhrObj(); -if(!_a&&dojo._Url){ -_b=(new dojo._Url(_b)).toString(); -} -if(d.config.cacheBust){ -_b+=""; -_b+=(_b.indexOf("?")==-1?"?":"&")+String(d.config.cacheBust).replace(/\W+/g,""); -} -var _e=["file:","chrome:","resource:"].some(function(_f){ -return String(_b).indexOf(_f)==0; -}); -if(_e){ -var _10=Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); -var _11=Components.classes["@mozilla.org/scriptableinputstream;1"].getService(Components.interfaces.nsIScriptableInputStream); -var _12=_10.newChannel(_b,null,null); -var _13=_12.open(); -_11.init(_13); -var str=_11.read(_13.available()); -_11.close(); -_13.close(); -return str; -}else{ -_d.open("GET",_b,false); -try{ -_d.send(null); -if(!d._isDocumentOk(_d)){ -var err=Error("Unable to load "+_b+" status:"+_d.status); -err.status=_d.status; -err.responseText=_d.responseText; -throw err; -} -} -catch(e){ -if(_c){ -return null; -} -throw e; -} -return _d.responseText; -} -}; -d._windowUnloaders=[]; -d.windowUnloaded=function(){ -var mll=d._windowUnloaders; -while(mll.length){ -(mll.pop())(); -} -}; -d.addOnWindowUnload=function(obj,_14){ -d._onto(d._windowUnloaders,obj,_14); -}; -var _15=[]; -var _16=null; -dojo._defaultContext=[window,document]; -dojo.pushContext=function(g,d){ -var old=[dojo.global,dojo.doc]; -_15.push(old); -var n; -if(!g&&!d){ -n=dojo._defaultContext; -}else{ -n=[g,d]; -if(!d&&dojo.isString(g)){ -var t=document.getElementById(g); -if(t.contentDocument){ -n=[t.contentWindow,t.contentDocument]; -} -} -} -_16=n; -dojo.setContext.apply(dojo,n); -return old; -}; -dojo.popContext=function(){ -var oc=_16; -if(!_15.length){ -return oc; -} -dojo.setContext.apply(dojo,_15.pop()); -return oc; -}; -dojo._inContext=function(g,d,f){ -var a=dojo._toArray(arguments); -f=a.pop(); -if(a.length==1){ -d=null; -} -dojo.pushContext(g,d); -var r=f(); -dojo.popContext(); -return r; -}; -})(); -dojo._initFired=false; -dojo._loadInit=function(e){ -dojo._initFired=true; -var _17=(e&&e.type)?e.type.toLowerCase():"load"; -if(arguments.callee.initialized||(_17!="domcontentloaded"&&_17!="load")){ -return; -} -arguments.callee.initialized=true; -if(dojo._inFlightCount==0){ -dojo._modulesLoaded(); -} -}; -if(!dojo.config.afterOnLoad){ -window.addEventListener("DOMContentLoaded",function(e){ -dojo._loadInit(e); -},false); -} -} +// a host environment specifically built for Mozilla extensions, but derived +// from the browser host environment +if(typeof window != 'undefined'){ + dojo.isBrowser = true; + dojo._name = "browser"; + + + // FIXME: PORTME + // http://developer.mozilla.org/en/mozIJSSubScriptLoader + + + // attempt to figure out the path to dojo if it isn't set in the config + (function(){ + var d = dojo; + // this is a scope protection closure. We set browser versions and grab + // the URL we were loaded from here. + + // FIXME: need to probably use a different reference to "document" to get the hosting XUL environment + + d.baseUrl = d.config.baseUrl; + + // fill in the rendering support information in dojo.render.* + var n = navigator; + var dua = n.userAgent; + var dav = n.appVersion; + var tv = parseFloat(dav); + + d.isMozilla = d.isMoz = tv; + if(d.isMoz){ + d.isFF = parseFloat(dua.split("Firefox/")[1]) || undefined; + } + + // FIXME + d.isQuirks = document.compatMode == "BackCompat"; + + // FIXME + // TODO: is the HTML LANG attribute relevant? + d.locale = dojo.config.locale || n.language.toLowerCase(); + + d._xhrObj = function(){ + return new XMLHttpRequest(); + } + + // monkey-patch _loadUri to handle file://, chrome://, and resource:// url's + var oldLoadUri = d._loadUri; + d._loadUri = function(uri, cb){ + var handleLocal = ["file:", "chrome:", "resource:"].some(function(prefix){ + return String(uri).indexOf(prefix) == 0; + }); + if(handleLocal){ + // see: + // http://developer.mozilla.org/en/mozIJSSubScriptLoader + var l = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Components.interfaces.mozIJSSubScriptLoader); + var value = l.loadSubScript(uri, d.global) + if(cb){ cb(value); } + return true; + }else{ + // otherwise, call the pre-existing version + return oldLoadUri.apply(d, arguments); + } + } + + // FIXME: PORTME + d._isDocumentOk = function(http){ + var stat = http.status || 0; + return (stat >= 200 && stat < 300) || // Boolean + stat == 304 || // allow any 2XX response code + stat == 1223 || // get it out of the cache + (!stat && (location.protocol=="file:" || location.protocol=="chrome:") ); + } + + // FIXME: PORTME + // var owloc = window.location+""; + // var base = document.getElementsByTagName("base"); + // var hasBase = (base && base.length > 0); + var hasBase = false; + + d._getText = function(/*URI*/ uri, /*Boolean*/ fail_ok){ + // summary: Read the contents of the specified uri and return those contents. + // uri: + // A relative or absolute uri. If absolute, it still must be in + // the same "domain" as we are. + // fail_ok: + // Default false. If fail_ok and loading fails, return null + // instead of throwing. + // returns: The response text. null is returned when there is a + // failure and failure is okay (an exception otherwise) + + // alert("_getText: " + uri); + + // NOTE: must be declared before scope switches ie. this._xhrObj() + var http = d._xhrObj(); + + if(!hasBase && dojo._Url){ + uri = (new dojo._Url(uri)).toString(); + } + if(d.config.cacheBust){ + //Make sure we have a string before string methods are used on uri + uri += ""; + uri += (uri.indexOf("?") == -1 ? "?" : "&") + String(d.config.cacheBust).replace(/\W+/g,""); + } + var handleLocal = ["file:", "chrome:", "resource:"].some(function(prefix){ + return String(uri).indexOf(prefix) == 0; + }); + if(handleLocal){ + // see: + // http://forums.mozillazine.org/viewtopic.php?p=921150#921150 + var ioService = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + var scriptableStream=Components + .classes["@mozilla.org/scriptableinputstream;1"] + .getService(Components.interfaces.nsIScriptableInputStream); + + var channel = ioService.newChannel(uri, null, null); + var input = channel.open(); + scriptableStream.init(input); + var str = scriptableStream.read(input.available()); + scriptableStream.close(); + input.close(); + return str; + }else{ + http.open('GET', uri, false); + try{ + http.send(null); + // alert(http); + if(!d._isDocumentOk(http)){ + var err = Error("Unable to load "+uri+" status:"+ http.status); + err.status = http.status; + err.responseText = http.responseText; + throw err; + } + }catch(e){ + if(fail_ok){ return null; } // null + // rethrow the exception + throw e; + } + return http.responseText; // String + } + } + + d._windowUnloaders = []; + + // FIXME: PORTME + d.windowUnloaded = function(){ + // summary: + // signal fired by impending window destruction. You may use + // dojo.addOnWIndowUnload() or dojo.connect() to this method to perform + // page/application cleanup methods. See dojo.addOnWindowUnload for more info. + var mll = d._windowUnloaders; + while(mll.length){ + (mll.pop())(); + } + } + + // FIXME: PORTME + d.addOnWindowUnload = function(/*Object?*/obj, /*String|Function?*/functionName){ + // summary: + // registers a function to be triggered when window.onunload fires. + // Be careful trying to modify the DOM or access JavaScript properties + // during this phase of page unloading: they may not always be available. + // Consider dojo.addOnUnload() if you need to modify the DOM or do heavy + // JavaScript work. + // example: + // | dojo.addOnWindowUnload(functionPointer) + // | dojo.addOnWindowUnload(object, "functionName") + // | dojo.addOnWindowUnload(object, function(){ /* ... */}); + + d._onto(d._windowUnloaders, obj, functionName); + } + + // XUL specific APIs + var contexts = []; + var current = null; + dojo._defaultContext = [ window, document ]; + + dojo.pushContext = function(/*Object|String?*/g, /*MDocumentElement?*/d){ + // summary: + // causes subsequent calls to Dojo methods to assume the + // passed object and, optionally, document as the default + // scopes to use. A 2-element array of the previous global and + // document are returned. + // description: + // dojo.pushContext treats contexts as a stack. The + // auto-detected contexts which are initially provided using + // dojo.setContext() require authors to keep state in order to + // "return" to a previous context, whereas the + // dojo.pushContext and dojo.popContext methods provide a more + // natural way to augment blocks of code to ensure that they + // execute in a different window or frame without issue. If + // called without any arguments, the default context (the + // context when Dojo is first loaded) is instead pushed into + // the stack. If only a single string is passed, a node in the + // intitial context's document is looked up and its + // contextWindow and contextDocument properties are used as + // the context to push. This means that iframes can be given + // an ID and code can be executed in the scope of the iframe's + // document in subsequent calls easily. + // g: + // The global context. If a string, the id of the frame to + // search for a context and document. + // d: + // The document element to execute subsequent code with. + var old = [dojo.global, dojo.doc]; + contexts.push(old); + var n; + if(!g && !d){ + n = dojo._defaultContext; + }else{ + n = [ g, d ]; + if(!d && dojo.isString(g)){ + var t = document.getElementById(g); + if(t.contentDocument){ + n = [t.contentWindow, t.contentDocument]; + } + } + } + current = n; + dojo.setContext.apply(dojo, n); + return old; // Array + }; + + dojo.popContext = function(){ + // summary: + // If the context stack contains elements, ensure that + // subsequent code executes in the *previous* context to the + // current context. The current context set ([global, + // document]) is returned. + var oc = current; + if(!contexts.length){ + return oc; + } + dojo.setContext.apply(dojo, contexts.pop()); + return oc; + }; + + // FIXME: + // don't really like the current arguments and order to + // _inContext, so don't make it public until it's right! + dojo._inContext = function(g, d, f){ + var a = dojo._toArray(arguments); + f = a.pop(); + if(a.length == 1){ + d = null; + } + dojo.pushContext(g, d); + var r = f(); + dojo.popContext(); + return r; + }; + + })(); + + dojo._initFired = false; + // BEGIN DOMContentLoaded, from Dean Edwards (http://dean.edwards.name/weblog/2006/06/again/) + dojo._loadInit = function(e){ + dojo._initFired = true; + // allow multiple calls, only first one will take effect + // A bug in khtml calls events callbacks for document for event which isnt supported + // for example a created contextmenu event calls DOMContentLoaded, workaround + var type = (e && e.type) ? e.type.toLowerCase() : "load"; + if(arguments.callee.initialized || (type != "domcontentloaded" && type != "load")){ return; } + arguments.callee.initialized = true; + if(dojo._inFlightCount == 0){ + dojo._modulesLoaded(); + } + } + + /* + (function(){ + var _w = window; + var _handleNodeEvent = function(evtName, fp){ + // summary: + // non-destructively adds the specified function to the node's + // evtName handler. + // evtName: should be in the form "onclick" for "onclick" handlers. + // Make sure you pass in the "on" part. + var oldHandler = _w[evtName] || function(){}; + _w[evtName] = function(){ + fp.apply(_w, arguments); + oldHandler.apply(_w, arguments); + }; + }; + // FIXME: PORT + // FIXME: dojo.unloaded requires dojo scope, so using anon function wrapper. + _handleNodeEvent("onbeforeunload", function() { dojo.unloaded(); }); + _handleNodeEvent("onunload", function() { dojo.windowUnloaded(); }); + })(); + */ + + + // FIXME: PORTME + // this event fires a lot, namely for all plugin XUL overlays and for + // all iframes (in addition to window navigations). We only want + // Dojo's to fire once..but we might care if pages navigate. We'll + // probably need an extension-specific API + if(!dojo.config.afterOnLoad){ + window.addEventListener("DOMContentLoaded",function(e){ + dojo._loadInit(e); + // console.log("DOM content loaded", e); + }, false); + } + +} //if (typeof window != 'undefined') + +//Register any module paths set up in djConfig. Need to do this +//in the hostenvs since hostenv_browser can read djConfig from a +//script tag's attribute. (function(){ -var mp=dojo.config["modulePaths"]; -if(mp){ -for(var _18 in mp){ -dojo.registerModulePath(_18,mp[_18]); -} -} + var mp = dojo.config["modulePaths"]; + if(mp){ + for(var param in mp){ + dojo.registerModulePath(param, mp[param]); + } + } })(); + +//Load debug code if necessary. if(dojo.config.isDebug){ -console.log=function(m){ -var s=Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService); -s.logStringMessage(m); -}; -console.debug=function(){ -}; + // logging stub for extension logging + console.log = function(m){ + var s = Components.classes["@mozilla.org/consoleservice;1"].getService( + Components.interfaces.nsIConsoleService + ); + s.logStringMessage(m); + } + console.debug = function(){ + console.log(dojo._toArray(arguments).join(" ")); + } + // FIXME: what about the rest of the console.* methods? And is there any way to reach into firebug and log into it directly? } diff --git a/lib/dojo/_base/_loader/hostenv_rhino.js b/lib/dojo/_base/_loader/hostenv_rhino.js index 9cd88271..ee9ad8b4 100644 --- a/lib/dojo/_base/_loader/hostenv_rhino.js +++ b/lib/dojo/_base/_loader/hostenv_rhino.js @@ -5,149 +5,204 @@ */ +/* +* Rhino host environment +*/ + if(dojo.config["baseUrl"]){ -dojo.baseUrl=dojo.config["baseUrl"]; + dojo.baseUrl = dojo.config["baseUrl"]; }else{ -dojo.baseUrl="./"; + dojo.baseUrl = "./"; } -dojo.locale=dojo.locale||String(java.util.Locale.getDefault().toString().replace("_","-").toLowerCase()); -dojo._name="rhino"; -dojo.isRhino=true; -if(typeof print=="function"){ -console.debug=print; + +dojo.locale = dojo.locale || String(java.util.Locale.getDefault().toString().replace('_','-').toLowerCase()); +dojo._name = 'rhino'; +dojo.isRhino = true; + +if(typeof print == "function"){ + console.debug = print; } + if(!("byId" in dojo)){ -dojo.byId=function(id,_1){ -if(id&&(typeof id=="string"||id instanceof String)){ -if(!_1){ -_1=document; -} -return _1.getElementById(id); -} -return id; -}; -} -dojo._isLocalUrl=function(_2){ -var _3=(new java.io.File(_2)).exists(); -if(!_3){ -var _4; -try{ -_4=(new java.net.URL(_2)).openStream(); -_4.close(); -} -finally{ -if(_4&&_4.close){ -_4.close(); -} -} -} -return _3; -}; -dojo._loadUri=function(_5,cb){ -try{ -var _6; -try{ -_6=dojo._isLocalUrl(_5); -} -catch(e){ -return false; -} -if(cb){ -var _7=(_6?readText:readUri)(_5,"UTF-8"); -if(!eval("'‏'").length){ -_7=String(_7).replace(/[\u200E\u200F\u202A-\u202E]/g,function(_8){ -return "\\u"+_8.charCodeAt(0).toString(16); -}); -} -cb(eval("("+_7+")")); -}else{ -load(_5); -} -return true; -} -catch(e){ -return false; -} -}; -dojo.exit=function(_9){ -quit(_9); -}; -function readText(_a,_b){ -_b=_b||"utf-8"; -var jf=new java.io.File(_a); -var is=new java.io.FileInputStream(jf); -return dj_readInputStream(is,_b); -}; -function readUri(_c,_d){ -var _e=(new java.net.URL(_c)).openConnection(); -_d=_d||_e.getContentEncoding()||"utf-8"; -var is=_e.getInputStream(); -return dj_readInputStream(is,_d); -}; -function dj_readInputStream(is,_f){ -var _10=new java.io.BufferedReader(new java.io.InputStreamReader(is,_f)); -try{ -var sb=new java.lang.StringBuffer(); -var _11=""; -while((_11=_10.readLine())!==null){ -sb.append(_11); -sb.append(java.lang.System.getProperty("line.separator")); -} -return sb.toString(); + dojo.byId = function(id, doc){ + if(id && (typeof id == "string" || id instanceof String)){ + if(!doc){ doc = document; } + return doc.getElementById(id); + } + return id; // assume it's a node + } } -finally{ -_10.close(); -} -}; -dojo._getText=function(uri,_12){ -try{ -var _13=dojo._isLocalUrl(uri); -var _14=(_13?readText:readUri)(uri,"UTF-8"); -if(_14!==null){ -_14+=""; + +dojo._isLocalUrl = function(/*String*/ uri) { + // summary: + // determines if URI is local or not. + + var local = (new java.io.File(uri)).exists(); + if(!local){ + var stream; + //Try remote URL. Allow this method to throw, + //but still do cleanup. + try{ + // try it as a file first, URL second + stream = (new java.net.URL(uri)).openStream(); + // close the stream so we don't leak resources + stream.close(); + }finally{ + if(stream && stream.close){ + stream.close(); + } + } + } + return local; } -return _14; + +// see comments in spidermonkey loadUri +dojo._loadUri = function(uri, cb){ + try{ + var local; + try{ + local = dojo._isLocalUrl(uri); + }catch(e){ + // no debug output; this failure just means the uri was not found. + return false; + } + + //FIXME: Use Rhino 1.6 native readFile/readUrl if available? + if(cb){ + var contents = (local ? readText : readUri)(uri, "UTF-8"); + + // patch up the input to eval until https://bugzilla.mozilla.org/show_bug.cgi?id=471005 is fixed. + if(!eval("'\u200f'").length){ + contents = String(contents).replace(/[\u200E\u200F\u202A-\u202E]/g, function(match){ + return "\\u" + match.charCodeAt(0).toString(16); + }) + } + + cb(eval('('+contents+')')); + }else{ + load(uri); + } + return true; + }catch(e){ + console.debug("rhino load('" + uri + "') failed. Exception: " + e); + return false; + } } -catch(e){ -if(_12){ -return null; -}else{ -throw e; + +dojo.exit = function(exitcode){ + quit(exitcode); } + +// reading a file from disk in Java is a humiliating experience by any measure. +// Lets avoid that and just get the freaking text +function readText(path, encoding){ + encoding = encoding || "utf-8"; + // NOTE: we intentionally avoid handling exceptions, since the caller will + // want to know + var jf = new java.io.File(path); + var is = new java.io.FileInputStream(jf); + return dj_readInputStream(is, encoding); } -}; -dojo.doc=typeof document!="undefined"?document:null; -dojo.body=function(){ -return document.body; -}; -if(typeof setTimeout=="undefined"||typeof clearTimeout=="undefined"){ -dojo._timeouts=[]; -clearTimeout=function(idx){ -if(!dojo._timeouts[idx]){ -return; + +function readUri(uri, encoding){ + var conn = (new java.net.URL(uri)).openConnection(); + encoding = encoding || conn.getContentEncoding() || "utf-8"; + var is = conn.getInputStream(); + return dj_readInputStream(is, encoding); } -dojo._timeouts[idx].stop(); -}; -setTimeout=function(_15,_16){ -var def={sleepTime:_16,hasSlept:false,run:function(){ -if(!this.hasSlept){ -this.hasSlept=true; -java.lang.Thread.currentThread().sleep(this.sleepTime); + +function dj_readInputStream(is, encoding){ + var input = new java.io.BufferedReader(new java.io.InputStreamReader(is, encoding)); + try { + var sb = new java.lang.StringBuffer(); + var line = ""; + while((line = input.readLine()) !== null){ + sb.append(line); + sb.append(java.lang.System.getProperty("line.separator")); + } + return sb.toString(); + } finally { + input.close(); + } } -try{ -_15(); + +dojo._getText = function(/*URI*/ uri, /*Boolean*/ fail_ok){ + // summary: Read the contents of the specified uri and return those contents. + // uri: + // A relative or absolute uri. + // fail_ok: + // Default false. If fail_ok and loading fails, return null + // instead of throwing. + // returns: The response text. null is returned when there is a + // failure and failure is okay (an exception otherwise) + try{ + var local = dojo._isLocalUrl(uri); + var text = (local ? readText : readUri)(uri, "UTF-8"); + if(text !== null){ + //Force JavaScript string. + text += ""; + } + return text; + }catch(e){ + if(fail_ok){ + return null; + }else{ + throw e; + } + } } -catch(e){ + +// summary: +// return the document object associated with the dojo.global +dojo.doc = typeof document != "undefined" ? document : null; + +dojo.body = function(){ + return document.body; } -}}; -var _17=new java.lang.Runnable(def); -var _18=new java.lang.Thread(_17); -_18.start(); -return dojo._timeouts.push(_18)-1; -}; + +// Supply setTimeout/clearTimeout implementations if they aren't already there +// Note: this assumes that we define both if one is not provided... there might +// be a better way to do this if there is a use case where one is defined but +// not the other +if(typeof setTimeout == "undefined" || typeof clearTimeout == "undefined"){ + dojo._timeouts = []; + clearTimeout = function(idx){ + if(!dojo._timeouts[idx]){ return; } + dojo._timeouts[idx].stop(); + } + + setTimeout = function(func, delay){ + // summary: provides timed callbacks using Java threads + + var def={ + sleepTime:delay, + hasSlept:false, + + run:function(){ + if(!this.hasSlept){ + this.hasSlept=true; + java.lang.Thread.currentThread().sleep(this.sleepTime); + } + try{ + func(); + }catch(e){ + console.debug("Error running setTimeout thread:" + e); + } + } + }; + + var runnable = new java.lang.Runnable(def); + var thread = new java.lang.Thread(runnable); + thread.start(); + return dojo._timeouts.push(thread)-1; + } } + +//Register any module paths set up in djConfig. Need to do this +//in the hostenvs since hostenv_browser can read djConfig from a +//script tag's attribute. if(dojo.config["modulePaths"]){ -for(var param in dojo.config["modulePaths"]){ -dojo.registerModulePath(param,dojo.config["modulePaths"][param]); -} + for(var param in dojo.config["modulePaths"]){ + dojo.registerModulePath(param, dojo.config["modulePaths"][param]); + } } diff --git a/lib/dojo/_base/_loader/hostenv_spidermonkey.js b/lib/dojo/_base/_loader/hostenv_spidermonkey.js index ca63f16c..17b21f5f 100644 --- a/lib/dojo/_base/_loader/hostenv_spidermonkey.js +++ b/lib/dojo/_base/_loader/hostenv_spidermonkey.js @@ -5,46 +5,83 @@ */ +/* + * SpiderMonkey host environment + */ + if(dojo.config["baseUrl"]){ -dojo.baseUrl=dojo.config["baseUrl"]; + dojo.baseUrl = dojo.config["baseUrl"]; }else{ -dojo.baseUrl="./"; + dojo.baseUrl = "./"; } -dojo._name="spidermonkey"; -dojo.isSpidermonkey=true; -dojo.exit=function(_1){ -quit(_1); + +dojo._name = 'spidermonkey'; + +/*===== +dojo.isSpidermonkey = { + // summary: Detect spidermonkey }; -if(typeof print=="function"){ -console.debug=print; -} -if(typeof line2pc=="undefined"){ -throw new Error("attempt to use SpiderMonkey host environment when no 'line2pc' global"); +=====*/ + +dojo.isSpidermonkey = true; +dojo.exit = function(exitcode){ + quit(exitcode); } -dojo._spidermonkeyCurrentFile=function(_2){ -var s=""; -try{ -throw Error("whatever"); + +if(typeof print == "function"){ + console.debug = print; } -catch(e){ -s=e.stack; + +if(typeof line2pc == 'undefined'){ + throw new Error("attempt to use SpiderMonkey host environment when no 'line2pc' global"); } -var _3=s.match(/[^@]*\.js/gi); -if(!_3){ -throw Error("could not parse stack string: '"+s+"'"); + +dojo._spidermonkeyCurrentFile = function(depth){ + // + // This is a hack that determines the current script file by parsing a + // generated stack trace (relying on the non-standard "stack" member variable + // of the SpiderMonkey Error object). + // + // If param depth is passed in, it'll return the script file which is that far down + // the stack, but that does require that you know how deep your stack is when you are + // calling. + // + var s = ''; + try{ + throw Error("whatever"); + }catch(e){ + s = e.stack; + } + // lines are like: bu_getCurrentScriptURI_spidermonkey("ScriptLoader.js")@burst/Runtime.js:101 + var matches = s.match(/[^@]*\.js/gi); + if(!matches){ + throw Error("could not parse stack string: '" + s + "'"); + } + var fname = (typeof depth != 'undefined' && depth) ? matches[depth + 1] : matches[matches.length - 1]; + if(!fname){ + throw Error("could not find file name in stack string '" + s + "'"); + } + //print("SpiderMonkeyRuntime got fname '" + fname + "' from stack string '" + s + "'"); + return fname; } -var _4=(typeof _2!="undefined"&&_2)?_3[_2+1]:_3[_3.length-1]; -if(!_4){ -throw Error("could not find file name in stack string '"+s+"'"); + +// print(dojo._spidermonkeyCurrentFile(0)); + +dojo._loadUri = function(uri){ + // spidermonkey load() evaluates the contents into the global scope (which + // is what we want). + // TODO: sigh, load() does not return a useful value. + // Perhaps it is returning the value of the last thing evaluated? + var ok = load(uri); + // console.log("spidermonkey load(", uri, ") returned ", ok); + return 1; } -return _4; -}; -dojo._loadUri=function(_5){ -var ok=load(_5); -return 1; -}; + +//Register any module paths set up in djConfig. Need to do this +//in the hostenvs since hostenv_browser can read djConfig from a +//script tag's attribute. if(dojo.config["modulePaths"]){ -for(var param in dojo.config["modulePaths"]){ -dojo.registerModulePath(param,dojo.config["modulePaths"][param]); -} + for(var param in dojo.config["modulePaths"]){ + dojo.registerModulePath(param, dojo.config["modulePaths"][param]); + } } diff --git a/lib/dojo/_base/_loader/loader.js b/lib/dojo/_base/_loader/loader.js index 3f31040a..9206de88 100644 --- a/lib/dojo/_base/_loader/loader.js +++ b/lib/dojo/_base/_loader/loader.js @@ -5,296 +5,800 @@ */ -if(!dojo._hasResource["dojo.foo"]){ -dojo._hasResource["dojo.foo"]=true; +if(!dojo._hasResource["dojo.foo"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.foo"] = true; +/* + * loader.js - A bootstrap module. Runs before the hostenv_*.js file. Contains + * all of the package loading methods. + */ + (function(){ -var d=dojo; -d.mixin(d,{_loadedModules:{},_inFlightCount:0,_hasResource:{},_modulePrefixes:{dojo:{name:"dojo",value:"."},doh:{name:"doh",value:"../util/doh"},tests:{name:"tests",value:"tests"}},_moduleHasPrefix:function(_1){ -var mp=d._modulePrefixes; -return !!(mp[_1]&&mp[_1].value); -},_getModulePrefix:function(_2){ -var mp=d._modulePrefixes; -if(d._moduleHasPrefix(_2)){ -return mp[_2].value; -} -return _2; -},_loadedUrls:[],_postLoad:false,_loaders:[],_unloaders:[],_loadNotifying:false}); -dojo._loadPath=function(_3,_4,cb){ -var _5=((_3.charAt(0)=="/"||_3.match(/^\w+:/))?"":d.baseUrl)+_3; -try{ -return !_4?d._loadUri(_5,cb):d._loadUriAndCheck(_5,_4,cb); -} -catch(e){ -console.error(e); -return false; -} -}; -dojo._loadUri=function(_6,cb){ -if(d._loadedUrls[_6]){ -return true; -} -d._inFlightCount++; -var _7=d._getText(_6,true); -if(_7){ -d._loadedUrls[_6]=true; -d._loadedUrls.push(_6); -if(cb){ -_7="("+_7+")"; -}else{ -_7=d._scopePrefix+_7+d._scopeSuffix; -} -if(!d.isIE){ -_7+="\r\n//@ sourceURL="+_6; -} -var _8=d["eval"](_7); -if(cb){ -cb(_8); -} -} -if(--d._inFlightCount==0&&d._postLoad&&d._loaders.length){ -setTimeout(function(){ -if(d._inFlightCount==0){ -d._callLoaded(); -} -},0); -} -return !!_7; -}; -dojo._loadUriAndCheck=function(_9,_a,cb){ -var ok=false; -try{ -ok=d._loadUri(_9,cb); -} -catch(e){ -console.error("failed loading "+_9+" with error: "+e); -} -return !!(ok&&d._loadedModules[_a]); -}; -dojo.loaded=function(){ -d._loadNotifying=true; -d._postLoad=true; -var _b=d._loaders; -d._loaders=[]; -for(var x=0;x<_b.length;x++){ -_b[x](); -} -d._loadNotifying=false; -if(d._postLoad&&d._inFlightCount==0&&_b.length){ -d._callLoaded(); -} -}; -dojo.unloaded=function(){ -var _c=d._unloaders; -while(_c.length){ -(_c.pop())(); -} -}; -d._onto=function(_d,_e,fn){ -if(!fn){ -_d.push(_e); -}else{ -if(fn){ -var _f=(typeof fn=="string")?_e[fn]:fn; -_d.push(function(){ -_f.call(_e); -}); -} -} -}; -dojo.ready=dojo.addOnLoad=function(obj,_10){ -d._onto(d._loaders,obj,_10); -if(d._postLoad&&d._inFlightCount==0&&!d._loadNotifying){ -d._callLoaded(); -} -}; -var dca=d.config.addOnLoad; -if(dca){ -d.addOnLoad[(dca instanceof Array?"apply":"call")](d,dca); -} -dojo._modulesLoaded=function(){ -if(d._postLoad){ -return; -} -if(d._inFlightCount>0){ -console.warn("files still in flight!"); -return; -} -d._callLoaded(); -}; -dojo._callLoaded=function(){ -if(typeof setTimeout=="object"||(d.config.useXDomain&&d.isOpera)){ -setTimeout(d.isAIR?function(){ -d.loaded(); -}:d._scopeName+".loaded();",0); -}else{ -d.loaded(); -} -}; -dojo._getModuleSymbols=function(_11){ -var _12=_11.split("."); -for(var i=_12.length;i>0;i--){ -var _13=_12.slice(0,i).join("."); -if(i==1&&!d._moduleHasPrefix(_13)){ -_12[0]="../"+_12[0]; -}else{ -var _14=d._getModulePrefix(_13); -if(_14!=_13){ -_12.splice(0,i,_14); -break; -} -} -} -return _12; -}; -dojo._global_omit_module_check=false; -dojo.loadInit=function(_15){ -_15(); -}; -dojo._loadModule=dojo.require=function(_16,_17){ -_17=d._global_omit_module_check||_17; -var _18=d._loadedModules[_16]; -if(_18){ -return _18; -} -var _19=d._getModuleSymbols(_16).join("/")+".js"; -var _1a=!_17?_16:null; -var ok=d._loadPath(_19,_1a); -if(!ok&&!_17){ -throw new Error("Could not load '"+_16+"'; last tried '"+_19+"'"); -} -if(!_17&&!d._isXDomain){ -_18=d._loadedModules[_16]; -if(!_18){ -throw new Error("symbol '"+_16+"' is not defined after loading '"+_19+"'"); -} -} -return _18; -}; -dojo.provide=function(_1b){ -_1b=_1b+""; -return (d._loadedModules[_1b]=d.getObject(_1b,true)); -}; -dojo.platformRequire=function(_1c){ -var _1d=_1c.common||[]; -var _1e=_1d.concat(_1c[d._name]||_1c["default"]||[]); -for(var x=0;x<_1e.length;x++){ -var _1f=_1e[x]; -if(_1f.constructor==Array){ -d._loadModule.apply(d,_1f); -}else{ -d._loadModule(_1f); -} -} -}; -dojo.requireIf=function(_20,_21){ -if(_20===true){ -var _22=[]; -for(var i=1;i0&&!(j==1&&_2d[0]=="")&&_2d[j]==".."&&_2d[j-1]!=".."){ -if(j==(_2d.length-1)){ -_2d.splice(j,1); -_2d[j-1]=""; -}else{ -_2d.splice(j-1,2); -j-=2; -} -} -} -} -_2a.path=_2d.join("/"); -} -} -} -} -uri=[]; -if(_2a.scheme){ -uri.push(_2a.scheme,":"); -} -if(_2a.authority){ -uri.push("//",_2a.authority); -} -uri.push(_2a.path); -if(_2a.query){ -uri.push("?",_2a.query); -} -if(_2a.fragment){ -uri.push("#",_2a.fragment); -} -} -this.uri=uri.join(""); -var r=this.uri.match(ore); -this.scheme=r[2]||(r[1]?"":n); -this.authority=r[4]||(r[3]?"":n); -this.path=r[5]; -this.query=r[7]||(r[6]?"":n); -this.fragment=r[9]||(r[8]?"":n); -if(this.authority!=n){ -r=this.authority.match(ire); -this.user=r[3]||n; -this.password=r[4]||n; -this.host=r[6]||r[7]; -this.port=r[9]||n; -} -}; -dojo._Url.prototype.toString=function(){ -return this.uri; -}; -dojo.moduleUrl=function(_2e,url){ -var loc=d._getModuleSymbols(_2e).join("/"); -if(!loc){ -return null; -} -if(loc.lastIndexOf("/")!=loc.length-1){ -loc+="/"; -} -var _2f=loc.indexOf(":"); -if(loc.charAt(0)!="/"&&(_2f==-1||_2f>loc.indexOf("/"))){ -loc=d.baseUrl+loc; -} -return new d._Url(loc,url); -}; + var d = dojo; + + d.mixin(d, { + _loadedModules: {}, + _inFlightCount: 0, + _hasResource: {}, + + _modulePrefixes: { + dojo: { name: "dojo", value: "." }, + // dojox: { name: "dojox", value: "../dojox" }, + // dijit: { name: "dijit", value: "../dijit" }, + doh: { name: "doh", value: "../util/doh" }, + tests: { name: "tests", value: "tests" } + }, + + _moduleHasPrefix: function(/*String*/module){ + // summary: checks to see if module has been established + var mp = d._modulePrefixes; + return !!(mp[module] && mp[module].value); // Boolean + }, + + _getModulePrefix: function(/*String*/module){ + // summary: gets the prefix associated with module + var mp = d._modulePrefixes; + if(d._moduleHasPrefix(module)){ + return mp[module].value; // String + } + return module; // String + }, + + _loadedUrls: [], + + //WARNING: + // This variable is referenced by packages outside of bootstrap: + // FloatingPane.js and undo/browser.js + _postLoad: false, + + //Egad! Lots of test files push on this directly instead of using dojo.addOnLoad. + _loaders: [], + _unloaders: [], + _loadNotifying: false + }); + + + dojo._loadPath = function(/*String*/relpath, /*String?*/module, /*Function?*/cb){ + // summary: + // Load a Javascript module given a relative path + // + // description: + // Loads and interprets the script located at relpath, which is + // relative to the script root directory. If the script is found but + // its interpretation causes a runtime exception, that exception is + // not caught by us, so the caller will see it. We return a true + // value if and only if the script is found. + // + // relpath: + // A relative path to a script (no leading '/', and typically ending + // in '.js'). + // module: + // A module whose existance to check for after loading a path. Can be + // used to determine success or failure of the load. + // cb: + // a callback function to pass the result of evaluating the script + + var uri = ((relpath.charAt(0) == '/' || relpath.match(/^\w+:/)) ? "" : d.baseUrl) + relpath; + try{ + return !module ? d._loadUri(uri, cb) : d._loadUriAndCheck(uri, module, cb); // Boolean + }catch(e){ + console.error(e); + return false; // Boolean + } + } + + dojo._loadUri = function(/*String*/uri, /*Function?*/cb){ + // summary: + // Loads JavaScript from a URI + // description: + // Reads the contents of the URI, and evaluates the contents. This is + // used to load modules as well as resource bundles. Returns true if + // it succeeded. Returns false if the URI reading failed. Throws if + // the evaluation throws. + // uri: a uri which points at the script to be loaded + // cb: + // a callback function to process the result of evaluating the script + // as an expression, typically used by the resource bundle loader to + // load JSON-style resources + + if(d._loadedUrls[uri]){ + return true; // Boolean + } + d._inFlightCount++; // block addOnLoad calls that arrive while we're busy downloading + var contents = d._getText(uri, true); + if(contents){ // not 404, et al + d._loadedUrls[uri] = true; + d._loadedUrls.push(uri); + if(cb){ + contents = '('+contents+')'; + }else{ + //Only do the scoping if no callback. If a callback is specified, + //it is most likely the i18n bundle stuff. + contents = d._scopePrefix + contents + d._scopeSuffix; + } + if(!d.isIE){ contents += "\r\n//@ sourceURL=" + uri; } // debugging assist for Firebug + var value = d["eval"](contents); + if(cb){ cb(value); } + } + // Check to see if we need to call _callLoaded() due to an addOnLoad() that arrived while we were busy downloading + if(--d._inFlightCount == 0 && d._postLoad && d._loaders.length){ + // We shouldn't be allowed to get here but Firefox allows an event + // (mouse, keybd, async xhrGet) to interrupt a synchronous xhrGet. + // If the current script block contains multiple require() statements, then after each + // require() returns, inFlightCount == 0, but we want to hold the _callLoaded() until + // all require()s are done since the out-of-sequence addOnLoad() presumably needs them all. + // setTimeout allows the next require() to start (if needed), and then we check this again. + setTimeout(function(){ + // If inFlightCount > 0, then multiple require()s are running sequentially and + // the next require() started after setTimeout() was executed but before we got here. + if(d._inFlightCount == 0){ + d._callLoaded(); + } + }, 0); + } + return !!contents; // Boolean: contents? true : false + } + + // FIXME: probably need to add logging to this method + dojo._loadUriAndCheck = function(/*String*/uri, /*String*/moduleName, /*Function?*/cb){ + // summary: calls loadUri then findModule and returns true if both succeed + var ok = false; + try{ + ok = d._loadUri(uri, cb); + }catch(e){ + console.error("failed loading " + uri + " with error: " + e); + } + return !!(ok && d._loadedModules[moduleName]); // Boolean + } + + dojo.loaded = function(){ + // summary: + // signal fired when initial environment and package loading is + // complete. You should use dojo.addOnLoad() instead of doing a + // direct dojo.connect() to this method in order to handle + // initialization tasks that require the environment to be + // initialized. In a browser host, declarative widgets will + // be constructed when this function finishes runing. + d._loadNotifying = true; + d._postLoad = true; + var mll = d._loaders; + + //Clear listeners so new ones can be added + //For other xdomain package loads after the initial load. + d._loaders = []; + + for(var x = 0; x < mll.length; x++){ + mll[x](); + } + + d._loadNotifying = false; + + //Make sure nothing else got added to the onload queue + //after this first run. If something did, and we are not waiting for any + //more inflight resources, run again. + if(d._postLoad && d._inFlightCount == 0 && mll.length){ + d._callLoaded(); + } + } + + dojo.unloaded = function(){ + // summary: + // signal fired by impending environment destruction. You should use + // dojo.addOnUnload() instead of doing a direct dojo.connect() to this + // method to perform page/application cleanup methods. See + // dojo.addOnUnload for more info. + var mll = d._unloaders; + while(mll.length){ + (mll.pop())(); + } + } + + d._onto = function(arr, obj, fn){ + if(!fn){ + arr.push(obj); + }else if(fn){ + var func = (typeof fn == "string") ? obj[fn] : fn; + arr.push(function(){ func.call(obj); }); + } + } + + dojo.ready = dojo.addOnLoad = function(/*Object*/obj, /*String|Function?*/functionName){ + // summary: + // Registers a function to be triggered after the DOM and dojo.require() calls + // have finished loading. + // + // description: + // Registers a function to be triggered after the DOM has finished + // loading and `dojo.require` modules have loaded. Widgets declared in markup + // have been instantiated if `djConfig.parseOnLoad` is true when this fires. + // + // Images and CSS files may or may not have finished downloading when + // the specified function is called. (Note that widgets' CSS and HTML + // code is guaranteed to be downloaded before said widgets are + // instantiated, though including css resouces BEFORE any script elements + // is highly recommended). + // + // example: + // Register an anonymous function to run when everything is ready + // | dojo.addOnLoad(function(){ doStuff(); }); + // + // example: + // Register a function to run when everything is ready by pointer: + // | var init = function(){ doStuff(); } + // | dojo.addOnLoad(init); + // + // example: + // Register a function to run scoped to `object`, either by name or anonymously: + // | dojo.addOnLoad(object, "functionName"); + // | dojo.addOnLoad(object, function(){ doStuff(); }); + + d._onto(d._loaders, obj, functionName); + + //Added for xdomain loading. dojo.addOnLoad is used to + //indicate callbacks after doing some dojo.require() statements. + //In the xdomain case, if all the requires are loaded (after initial + //page load), then immediately call any listeners. + if(d._postLoad && d._inFlightCount == 0 && !d._loadNotifying){ + d._callLoaded(); + } + } + + //Support calling dojo.addOnLoad via djConfig.addOnLoad. Support all the + //call permutations of dojo.addOnLoad. Mainly useful when dojo is added + //to the page after the page has loaded. + var dca = d.config.addOnLoad; + if(dca){ + d.addOnLoad[(dca instanceof Array ? "apply" : "call")](d, dca); + } + + dojo._modulesLoaded = function(){ + if(d._postLoad){ return; } + if(d._inFlightCount > 0){ + console.warn("files still in flight!"); + return; + } + d._callLoaded(); + } + + dojo._callLoaded = function(){ + + // The "object" check is for IE, and the other opera check fixes an + // issue in Opera where it could not find the body element in some + // widget test cases. For 0.9, maybe route all browsers through the + // setTimeout (need protection still for non-browser environments + // though). This might also help the issue with FF 2.0 and freezing + // issues where we try to do sync xhr while background css images are + // being loaded (trac #2572)? Consider for 0.9. + if(typeof setTimeout == "object" || (d.config.useXDomain && d.isOpera)){ + setTimeout( + d.isAIR ? function(){ d.loaded(); } : d._scopeName + ".loaded();", + 0); + }else{ + d.loaded(); + } + } + + dojo._getModuleSymbols = function(/*String*/modulename){ + // summary: + // Converts a module name in dotted JS notation to an array + // representing the path in the source tree + var syms = modulename.split("."); + for(var i = syms.length; i>0; i--){ + var parentModule = syms.slice(0, i).join("."); + if(i == 1 && !d._moduleHasPrefix(parentModule)){ + // Support default module directory (sibling of dojo) for top-level modules + syms[0] = "../" + syms[0]; + }else{ + var parentModulePath = d._getModulePrefix(parentModule); + if(parentModulePath != parentModule){ + syms.splice(0, i, parentModulePath); + break; + } + } + } + return syms; // Array + } + + dojo._global_omit_module_check = false; + + dojo.loadInit = function(/*Function*/init){ + // summary: + // Executes a function that needs to be executed for the loader's dojo.requireIf + // resolutions to work. This is needed mostly for the xdomain loader case where + // a function needs to be executed to set up the possible values for a dojo.requireIf + // call. + // init: + // a function reference. Executed immediately. + // description: This function is mainly a marker for the xdomain loader to know parts of + // code that needs be executed outside the function wrappper that is placed around modules. + // The init function could be executed more than once, and it should make no assumptions + // on what is loaded, or what modules are available. Only the functionality in Dojo Base + // is allowed to be used. Avoid using this method. For a valid use case, + // see the source for dojox.gfx. + init(); + } + + dojo._loadModule = dojo.require = function(/*String*/moduleName, /*Boolean?*/omitModuleCheck){ + // summary: + // loads a Javascript module from the appropriate URI + // moduleName: + // module name to load, using periods for separators, + // e.g. "dojo.date.locale". Module paths are de-referenced by dojo's + // internal mapping of locations to names and are disambiguated by + // longest prefix. See `dojo.registerModulePath()` for details on + // registering new modules. + // omitModuleCheck: + // if `true`, omitModuleCheck skips the step of ensuring that the + // loaded file actually defines the symbol it is referenced by. + // For example if it called as `dojo.require("a.b.c")` and the + // file located at `a/b/c.js` does not define an object `a.b.c`, + // and exception will be throws whereas no exception is raised + // when called as `dojo.require("a.b.c", true)` + // description: + // Modules are loaded via dojo.require by using one of two loaders: the normal loader + // and the xdomain loader. The xdomain loader is used when dojo was built with a + // custom build that specified loader=xdomain and the module lives on a modulePath + // that is a whole URL, with protocol and a domain. The versions of Dojo that are on + // the Google and AOL CDNs use the xdomain loader. + // + // If the module is loaded via the xdomain loader, it is an asynchronous load, since + // the module is added via a dynamically created script tag. This + // means that dojo.require() can return before the module has loaded. However, this + // should only happen in the case where you do dojo.require calls in the top-level + // HTML page, or if you purposely avoid the loader checking for dojo.require + // dependencies in your module by using a syntax like dojo["require"] to load the module. + // + // Sometimes it is useful to not have the loader detect the dojo.require calls in the + // module so that you can dynamically load the modules as a result of an action on the + // page, instead of right at module load time. + // + // Also, for script blocks in an HTML page, the loader does not pre-process them, so + // it does not know to download the modules before the dojo.require calls occur. + // + // So, in those two cases, when you want on-the-fly module loading or for script blocks + // in the HTML page, special care must be taken if the dojo.required code is loaded + // asynchronously. To make sure you can execute code that depends on the dojo.required + // modules, be sure to add the code that depends on the modules in a dojo.addOnLoad() + // callback. dojo.addOnLoad waits for all outstanding modules to finish loading before + // executing. Example: + // + // | + // + // This type of syntax works with both xdomain and normal loaders, so it is good + // practice to always use this idiom for on-the-fly code loading and in HTML script + // blocks. If at some point you change loaders and where the code is loaded from, + // it will all still work. + // + // More on how dojo.require + // `dojo.require("A.B")` first checks to see if symbol A.B is + // defined. If it is, it is simply returned (nothing to do). + // + // If it is not defined, it will look for `A/B.js` in the script root + // directory. + // + // `dojo.require` throws an excpetion if it cannot find a file + // to load, or if the symbol `A.B` is not defined after loading. + // + // It returns the object `A.B`, but note the caveats above about on-the-fly loading and + // HTML script blocks when the xdomain loader is loading a module. + // + // `dojo.require()` does nothing about importing symbols into + // the current namespace. It is presumed that the caller will + // take care of that. For example, to import all symbols into a + // local block, you might write: + // + // | with (dojo.require("A.B")) { + // | ... + // | } + // + // And to import just the leaf symbol to a local variable: + // + // | var B = dojo.require("A.B"); + // | ... + // returns: the required namespace object + omitModuleCheck = d._global_omit_module_check || omitModuleCheck; + + //Check if it is already loaded. + var module = d._loadedModules[moduleName]; + if(module){ + return module; + } + + // convert periods to slashes + var relpath = d._getModuleSymbols(moduleName).join("/") + '.js'; + + var modArg = !omitModuleCheck ? moduleName : null; + var ok = d._loadPath(relpath, modArg); + + if(!ok && !omitModuleCheck){ + throw new Error("Could not load '" + moduleName + "'; last tried '" + relpath + "'"); + } + + // check that the symbol was defined + // Don't bother if we're doing xdomain (asynchronous) loading. + if(!omitModuleCheck && !d._isXDomain){ + // pass in false so we can give better error + module = d._loadedModules[moduleName]; + if(!module){ + throw new Error("symbol '" + moduleName + "' is not defined after loading '" + relpath + "'"); + } + } + + return module; + } + + dojo.provide = function(/*String*/ resourceName){ + // summary: + // Register a resource with the package system. Works in conjunction with `dojo.require` + // + // description: + // Each javascript source file is called a resource. When a + // resource is loaded by the browser, `dojo.provide()` registers + // that it has been loaded. + // + // Each javascript source file must have at least one + // `dojo.provide()` call at the top of the file, corresponding to + // the file name. For example, `js/dojo/foo.js` must have + // `dojo.provide("dojo.foo");` before any calls to + // `dojo.require()` are made. + // + // For backwards compatibility reasons, in addition to registering + // the resource, `dojo.provide()` also ensures that the javascript + // object for the module exists. For example, + // `dojo.provide("dojox.data.FlickrStore")`, in addition to + // registering that `FlickrStore.js` is a resource for the + // `dojox.data` module, will ensure that the `dojox.data` + // javascript object exists, so that calls like + // `dojo.data.foo = function(){ ... }` don't fail. + // + // In the case of a build where multiple javascript source files + // are combined into one bigger file (similar to a .lib or .jar + // file), that file may contain multiple dojo.provide() calls, to + // note that it includes multiple resources. + // + // resourceName: String + // A dot-sperated string identifying a resource. + // + // example: + // Safely create a `my` object, and make dojo.require("my.CustomModule") work + // | dojo.provide("my.CustomModule"); + + //Make sure we have a string. + resourceName = resourceName + ""; + return (d._loadedModules[resourceName] = d.getObject(resourceName, true)); // Object + } + + //Start of old bootstrap2: + + dojo.platformRequire = function(/*Object*/modMap){ + // summary: + // require one or more modules based on which host environment + // Dojo is currently operating in + // description: + // This method takes a "map" of arrays which one can use to + // optionally load dojo modules. The map is indexed by the + // possible dojo.name_ values, with two additional values: + // "default" and "common". The items in the "default" array will + // be loaded if none of the other items have been choosen based on + // dojo.name_, set by your host environment. The items in the + // "common" array will *always* be loaded, regardless of which + // list is chosen. + // example: + // | dojo.platformRequire({ + // | browser: [ + // | "foo.sample", // simple module + // | "foo.test", + // | ["foo.bar.baz", true] // skip object check in _loadModule (dojo.require) + // | ], + // | default: [ "foo.sample._base" ], + // | common: [ "important.module.common" ] + // | }); + + var common = modMap.common || []; + var result = common.concat(modMap[d._name] || modMap["default"] || []); + + for(var x=0; x + // | + d._modulePrefixes[module] = { name: module, value: prefix }; + } + + dojo.requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){ + // summary: + // Declares translated resources and loads them if necessary, in the + // same style as dojo.require. Contents of the resource bundle are + // typically strings, but may be any name/value pair, represented in + // JSON format. See also `dojo.i18n.getLocalization`. + // + // description: + // Load translated resource bundles provided underneath the "nls" + // directory within a package. Translated resources may be located in + // different packages throughout the source tree. + // + // Each directory is named for a locale as specified by RFC 3066, + // (http://www.ietf.org/rfc/rfc3066.txt), normalized in lowercase. + // Note that the two bundles in the example do not define all the + // same variants. For a given locale, bundles will be loaded for + // that locale and all more general locales above it, including a + // fallback at the root directory. For example, a declaration for + // the "de-at" locale will first load `nls/de-at/bundleone.js`, + // then `nls/de/bundleone.js` and finally `nls/bundleone.js`. The + // data will be flattened into a single Object so that lookups + // will follow this cascading pattern. An optional build step can + // preload the bundles to avoid data redundancy and the multiple + // network hits normally required to load these resources. + // + // moduleName: + // name of the package containing the "nls" directory in which the + // bundle is found + // + // bundleName: + // bundle name, i.e. the filename without the '.js' suffix. Using "nls" as a + // a bundle name is not supported, since "nls" is the name of the folder + // that holds bundles. Using "nls" as the bundle name will cause problems + // with the custom build. + // + // locale: + // the locale to load (optional) By default, the browser's user + // locale as defined by dojo.locale + // + // availableFlatLocales: + // A comma-separated list of the available, flattened locales for this + // bundle. This argument should only be set by the build process. + // + // example: + // A particular widget may define one or more resource bundles, + // structured in a program as follows, where moduleName is + // mycode.mywidget and bundleNames available include bundleone and + // bundletwo: + // | ... + // | mycode/ + // | mywidget/ + // | nls/ + // | bundleone.js (the fallback translation, English in this example) + // | bundletwo.js (also a fallback translation) + // | de/ + // | bundleone.js + // | bundletwo.js + // | de-at/ + // | bundleone.js + // | en/ + // | (empty; use the fallback translation) + // | en-us/ + // | bundleone.js + // | en-gb/ + // | bundleone.js + // | es/ + // | bundleone.js + // | bundletwo.js + // | ...etc + // | ... + // + + d.require("dojo.i18n"); + d.i18n._requireLocalization.apply(d.hostenv, arguments); + }; + + + var ore = new RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$"), + ire = new RegExp("^((([^\\[:]+):)?([^@]+)@)?(\\[([^\\]]+)\\]|([^\\[:]*))(:([0-9]+))?$"); + + dojo._Url = function(/*dojo._Url|String...*/){ + // summary: + // Constructor to create an object representing a URL. + // It is marked as private, since we might consider removing + // or simplifying it. + // description: + // Each argument is evaluated in order relative to the next until + // a canonical uri is produced. To get an absolute Uri relative to + // the current document use: + // new dojo._Url(document.baseURI, url) + + var n = null, + _a = arguments, + uri = [_a[0]]; + // resolve uri components relative to each other + for(var i = 1; i<_a.length; i++){ + if(!_a[i]){ continue; } + + // Safari doesn't support this.constructor so we have to be explicit + // FIXME: Tracked (and fixed) in Webkit bug 3537. + // http://bugs.webkit.org/show_bug.cgi?id=3537 + var relobj = new d._Url(_a[i]+""), + uriobj = new d._Url(uri[0]+""); + + if( + relobj.path == "" && + !relobj.scheme && + !relobj.authority && + !relobj.query + ){ + if(relobj.fragment != n){ + uriobj.fragment = relobj.fragment; + } + relobj = uriobj; + }else if(!relobj.scheme){ + relobj.scheme = uriobj.scheme; + + if(!relobj.authority){ + relobj.authority = uriobj.authority; + + if(relobj.path.charAt(0) != "/"){ + var path = uriobj.path.substring(0, + uriobj.path.lastIndexOf("/") + 1) + relobj.path; + + var segs = path.split("/"); + for(var j = 0; j < segs.length; j++){ + if(segs[j] == "."){ + // flatten "./" references + if(j == segs.length - 1){ + segs[j] = ""; + }else{ + segs.splice(j, 1); + j--; + } + }else if(j > 0 && !(j == 1 && segs[0] == "") && + segs[j] == ".." && segs[j-1] != ".."){ + // flatten "../" references + if(j == (segs.length - 1)){ + segs.splice(j, 1); + segs[j - 1] = ""; + }else{ + segs.splice(j - 1, 2); + j -= 2; + } + } + } + relobj.path = segs.join("/"); + } + } + } + + uri = []; + if(relobj.scheme){ + uri.push(relobj.scheme, ":"); + } + if(relobj.authority){ + uri.push("//", relobj.authority); + } + uri.push(relobj.path); + if(relobj.query){ + uri.push("?", relobj.query); + } + if(relobj.fragment){ + uri.push("#", relobj.fragment); + } + } + + this.uri = uri.join(""); + + // break the uri into its main components + var r = this.uri.match(ore); + + this.scheme = r[2] || (r[1] ? "" : n); + this.authority = r[4] || (r[3] ? "" : n); + this.path = r[5]; // can never be undefined + this.query = r[7] || (r[6] ? "" : n); + this.fragment = r[9] || (r[8] ? "" : n); + + if(this.authority != n){ + // server based naming authority + r = this.authority.match(ire); + + this.user = r[3] || n; + this.password = r[4] || n; + this.host = r[6] || r[7]; // ipv6 || ipv4 + this.port = r[9] || n; + } + } + + dojo._Url.prototype.toString = function(){ return this.uri; }; + + dojo.moduleUrl = function(/*String*/module, /*dojo._Url||String*/url){ + // summary: + // Returns a `dojo._Url` object relative to a module. + // example: + // | var pngPath = dojo.moduleUrl("acme","images/small.png"); + // | console.dir(pngPath); // list the object properties + // | // create an image and set it's source to pngPath's value: + // | var img = document.createElement("img"); + // | // NOTE: we assign the string representation of the url object + // | img.src = pngPath.toString(); + // | // add our image to the document + // | dojo.body().appendChild(img); + // example: + // you may de-reference as far as you like down the package + // hierarchy. This is sometimes handy to avoid lenghty relative + // urls or for building portable sub-packages. In this example, + // the `acme.widget` and `acme.util` directories may be located + // under different roots (see `dojo.registerModulePath`) but the + // the modules which reference them can be unaware of their + // relative locations on the filesystem: + // | // somewhere in a configuration block + // | dojo.registerModulePath("acme.widget", "../../acme/widget"); + // | dojo.registerModulePath("acme.util", "../../util"); + // | + // | // ... + // | + // | // code in a module using acme resources + // | var tmpltPath = dojo.moduleUrl("acme.widget","templates/template.html"); + // | var dataPath = dojo.moduleUrl("acme.util","resources/data.json"); + + var loc = d._getModuleSymbols(module).join('/'); + if(!loc){ return null; } + if(loc.lastIndexOf("/") != loc.length-1){ + loc += "/"; + } + + //If the path is an absolute path (starts with a / or is on another + //domain/xdomain) then don't add the baseUrl. + var colonIndex = loc.indexOf(":"); + if(loc.charAt(0) != "/" && (colonIndex == -1 || colonIndex > loc.indexOf("/"))){ + loc = d.baseUrl + loc; + } + + return new d._Url(loc, url); // dojo._Url + } })(); + } diff --git a/lib/dojo/_base/_loader/loader_debug.js b/lib/dojo/_base/_loader/loader_debug.js index a28040f5..fa26d8ef 100644 --- a/lib/dojo/_base/_loader/loader_debug.js +++ b/lib/dojo/_base/_loader/loader_debug.js @@ -5,55 +5,82 @@ */ -if(!dojo._hasResource["dojo._base._loader.loader_debug"]){ -dojo._hasResource["dojo._base._loader.loader_debug"]=true; +if(!dojo._hasResource["dojo._base._loader.loader_debug"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base._loader.loader_debug"] = true; dojo.provide("dojo._base._loader.loader_debug"); -dojo.nonDebugProvide=dojo.provide; -dojo.provide=function(_1){ -var _2=dojo["_xdDebugQueue"]; -if(_2&&_2.length>0&&_1==_2["currentResourceName"]){ -if(dojo.isAIR){ -window.setTimeout(function(){ -dojo._xdDebugFileLoaded(_1); -},1); -}else{ -window.setTimeout(dojo._scopeName+"._xdDebugFileLoaded('"+_1+"')",1); -} -} -return dojo.nonDebugProvide.apply(dojo,arguments); -}; -dojo._xdDebugFileLoaded=function(_3){ -if(!dojo._xdDebugScopeChecked){ -if(dojo._scopeName!="dojo"){ -window.dojo=window[dojo.config.scopeMap[0][1]]; -window.dijit=window[dojo.config.scopeMap[1][1]]; -window.dojox=window[dojo.config.scopeMap[2][1]]; -} -dojo._xdDebugScopeChecked=true; -} -var _4=dojo._xdDebugQueue; -if(_3&&_3==_4.currentResourceName){ -_4.shift(); -} -if(_4.length==0){ -dojo._xdWatchInFlight(); -} -if(_4.length==0){ -_4.currentResourceName=null; -for(var _5 in dojo._xdInFlight){ -if(dojo._xdInFlight[_5]===true){ -return; -} -} -dojo._xdNotifyLoaded(); -}else{ -if(_3==_4.currentResourceName){ -_4.currentResourceName=_4[0].resourceName; -var _6=document.createElement("script"); -_6.type="text/javascript"; -_6.src=_4[0].resourcePath; -document.getElementsByTagName("head")[0].appendChild(_6); + +//Override dojo.provide, so we can trigger the next +//script tag for the next local module. We can only add one +//at a time because there are browsers that execute script tags +//in the order that the code is received, and not in the DOM order. +dojo.nonDebugProvide = dojo.provide; + +dojo.provide = function(resourceName){ + var dbgQueue = dojo["_xdDebugQueue"]; + if(dbgQueue && dbgQueue.length > 0 && resourceName == dbgQueue["currentResourceName"]){ + //Set a timeout so the module can be executed into existence. Normally the + //dojo.provide call in a module is the first line. Don't want to risk attaching + //another script tag until the current one finishes executing. + if(dojo.isAIR){ + window.setTimeout(function(){dojo._xdDebugFileLoaded(resourceName);}, 1); + }else{ + window.setTimeout(dojo._scopeName + "._xdDebugFileLoaded('" + resourceName + "')", 1); + } + } + + return dojo.nonDebugProvide.apply(dojo, arguments); } + +dojo._xdDebugFileLoaded = function(resourceName){ + + if(!dojo._xdDebugScopeChecked){ + //If using a scoped dojo, we need to expose dojo as a real global + //for the debugAtAllCosts stuff to work. + if(dojo._scopeName != "dojo"){ + window.dojo = window[dojo.config.scopeMap[0][1]]; + window.dijit = window[dojo.config.scopeMap[1][1]]; + window.dojox = window[dojo.config.scopeMap[2][1]]; + } + + dojo._xdDebugScopeChecked = true; + } + + var dbgQueue = dojo._xdDebugQueue; + + if(resourceName && resourceName == dbgQueue.currentResourceName){ + dbgQueue.shift(); + } + + if(dbgQueue.length == 0){ + //Check for more modules that need debug loading. + //dojo._xdWatchInFlight will add more things to the debug + //queue if they just recently loaded but it was not detected + //between the dojo._xdWatchInFlight intervals. + dojo._xdWatchInFlight(); + } + + if(dbgQueue.length == 0){ + dbgQueue.currentResourceName = null; + + //Make sure nothing else is in flight. + //If something is still in flight, then it still + //needs to be added to debug queue after it loads. + for(var param in dojo._xdInFlight){ + if(dojo._xdInFlight[param] === true){ + return; + } + } + + dojo._xdNotifyLoaded(); + }else{ + if(resourceName == dbgQueue.currentResourceName){ + dbgQueue.currentResourceName = dbgQueue[0].resourceName; + var element = document.createElement("script"); + element.type = "text/javascript"; + element.src = dbgQueue[0].resourcePath; + document.getElementsByTagName("head")[0].appendChild(element); + } + } } -}; + } diff --git a/lib/dojo/_base/_loader/loader_xd.js b/lib/dojo/_base/_loader/loader_xd.js index 2ecab3db..c60b86b0 100644 --- a/lib/dojo/_base/_loader/loader_xd.js +++ b/lib/dojo/_base/_loader/loader_xd.js @@ -5,461 +5,719 @@ */ -if(!dojo._hasResource["dojo._base._loader.loader_xd"]){ -dojo._hasResource["dojo._base._loader.loader_xd"]=true; +if(!dojo._hasResource["dojo._base._loader.loader_xd"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base._loader.loader_xd"] = true; +//Cross-domain resource loader. dojo.provide("dojo._base._loader.loader_xd"); -dojo._xdReset=function(){ -dojo._isXDomain=dojo.config.useXDomain||false; -dojo._xdClearInterval(); -dojo._xdInFlight={}; -dojo._xdOrderedReqs=[]; -dojo._xdDepMap={}; -dojo._xdContents=[]; -dojo._xdDefList=[]; -}; -dojo._xdClearInterval=function(){ -if(dojo._xdTimer){ -clearInterval(dojo._xdTimer); -dojo._xdTimer=0; -} -}; -dojo._xdReset(); -dojo._xdCreateResource=function(_1,_2,_3){ -var _4=_1.replace(/(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg,""); -var _5=[]; -var _6=/dojo.(require|requireIf|provide|requireAfterIf|platformRequire|requireLocalization)\s*\(([\w\W]*?)\)/mg; -var _7; -while((_7=_6.exec(_4))!=null){ -if(_7[1]=="requireLocalization"){ -eval(_7[0]); -}else{ -_5.push("\""+_7[1]+"\", "+_7[2]); -} -} -var _8=[]; -_8.push(dojo._scopeName+"._xdResourceLoaded(function("+dojo._scopePrefixArgs+"){\n"); -var _9=dojo._xdExtractLoadInits(_1); -if(_9){ -_1=_9[0]; -for(var i=1;i<_9.length;i++){ -_8.push(_9[i]+";\n"); -} -} -_8.push("return {"); -if(_5.length>0){ -_8.push("depends: ["); -for(i=0;i<_5.length;i++){ -if(i>0){ -_8.push(",\n"); -} -_8.push("["+_5[i]+"]"); -} -_8.push("],"); -} -_8.push("\ndefineResource: function("+dojo._scopePrefixArgs+"){"); -if(!dojo.config["debugAtAllCosts"]||_2=="dojo._base._loader.loader_debug"){ -_8.push(_1); -} -_8.push("\n}, resourceName: '"+_2+"', resourcePath: '"+_3+"'};});"); -return _8.join(""); -}; -dojo._xdExtractLoadInits=function(_a){ -var _b=/dojo.loadInit\s*\(/g; -_b.lastIndex=0; -var _c=/[\(\)]/g; -_c.lastIndex=0; -var _d=[]; -var _e; -while((_e=_b.exec(_a))){ -_c.lastIndex=_b.lastIndex; -var _f=1; -var _10; -while((_10=_c.exec(_a))){ -if(_10[0]==")"){ -_f-=1; -}else{ -_f+=1; -} -if(_f==0){ -break; -} -} -if(_f!=0){ -throw "unmatched paren around character "+_c.lastIndex+" in: "+_a; -} -var _11=_b.lastIndex-_e[0].length; -_d.push(_a.substring(_11,_c.lastIndex)); -var _12=_c.lastIndex-_11; -_a=_a.substring(0,_11)+_a.substring(_c.lastIndex,_a.length); -_b.lastIndex=_c.lastIndex-_12; -_b.lastIndex=_c.lastIndex; -} -if(_d.length>0){ -_d.unshift(_a); -} -return (_d.length?_d:null); -}; -dojo._xdIsXDomainPath=function(_13){ -var _14=_13.indexOf(":"); -var _15=_13.indexOf("/"); -if(_14>0&&_14<_15){ -return true; -}else{ -var url=dojo.baseUrl; -_14=url.indexOf(":"); -_15=url.indexOf("/"); -if(_14>0&&_14<_15&&(!location.host||url.indexOf("http://"+location.host)!=0)){ -return true; -} -} -return false; -}; -dojo._loadPath=function(_16,_17,cb){ -var _18=dojo._xdIsXDomainPath(_16); -dojo._isXDomain|=_18; -var uri=((_16.charAt(0)=="/"||_16.match(/^\w+:/))?"":dojo.baseUrl)+_16; -try{ -return ((!_17||dojo._isXDomain)?dojo._loadUri(uri,cb,_18,_17):dojo._loadUriAndCheck(uri,_17,cb)); -} -catch(e){ -console.error(e); -return false; -} -}; -dojo._xdCharSet="utf-8"; -dojo._loadUri=function(uri,cb,_19,_1a){ -if(dojo._loadedUrls[uri]){ -return 1; -} -if(dojo._isXDomain&&_1a&&_1a!="dojo.i18n"){ -dojo._xdOrderedReqs.push(_1a); -if(_19||uri.indexOf("/nls/")==-1){ -dojo._xdInFlight[_1a]=true; -dojo._inFlightCount++; -} -if(!dojo._xdTimer){ -if(dojo.isAIR){ -dojo._xdTimer=setInterval(function(){ -dojo._xdWatchInFlight(); -},100); -}else{ -dojo._xdTimer=setInterval(dojo._scopeName+"._xdWatchInFlight();",100); -} -} -dojo._xdStartTime=(new Date()).getTime(); -} -if(_19){ -var _1b=uri.lastIndexOf("."); -if(_1b<=0){ -_1b=uri.length-1; -} -var _1c=uri.substring(0,_1b)+".xd"; -if(_1b!=uri.length-1){ -_1c+=uri.substring(_1b,uri.length); -} -if(dojo.isAIR){ -_1c=_1c.replace("app:/","/"); -} -var _1d=document.createElement("script"); -_1d.type="text/javascript"; -if(dojo._xdCharSet){ -_1d.charset=dojo._xdCharSet; -} -_1d.src=_1c; -if(!dojo.headElement){ -dojo._headElement=document.getElementsByTagName("head")[0]; -if(!dojo._headElement){ -dojo._headElement=document.getElementsByTagName("html")[0]; -} -} -dojo._headElement.appendChild(_1d); -}else{ -var _1e=dojo._getText(uri,null,true); -if(_1e==null){ -return 0; -} -if(dojo._isXDomain&&uri.indexOf("/nls/")==-1&&_1a!="dojo.i18n"){ -var res=dojo._xdCreateResource(_1e,_1a,uri); -dojo.eval(res); -}else{ -if(cb){ -_1e="("+_1e+")"; -}else{ -_1e=dojo._scopePrefix+_1e+dojo._scopeSuffix; -} -var _1f=dojo["eval"](_1e+"\r\n//@ sourceURL="+uri); -if(cb){ -cb(_1f); -} -} -} -dojo._loadedUrls[uri]=true; -dojo._loadedUrls.push(uri); -return true; -}; -dojo._xdResourceLoaded=function(res){ -res=res.apply(dojo.global,dojo._scopeArgs); -var _20=res.depends; -var _21=null; -var _22=null; -var _23=[]; -if(_20&&_20.length>0){ -var dep=null; -var _24=0; -var _25=false; -for(var i=0;i<_20.length;i++){ -dep=_20[i]; -if(dep[0]=="provide"){ -_23.push(dep[1]); -}else{ -if(!_21){ -_21=[]; -} -if(!_22){ -_22=[]; -} -var _26=dojo._xdUnpackDependency(dep); -if(_26.requires){ -_21=_21.concat(_26.requires); -} -if(_26.requiresAfter){ -_22=_22.concat(_26.requiresAfter); -} -} -var _27=dep[0]; -var _28=_27.split("."); -if(_28.length==2){ -dojo[_28[0]][_28[1]].apply(dojo[_28[0]],dep.slice(1)); -}else{ -dojo[_27].apply(dojo,dep.slice(1)); -} -} -if(_23.length==1&&_23[0]=="dojo._base._loader.loader_debug"){ -res.defineResource(dojo); -}else{ -var _29=dojo._xdContents.push({content:res.defineResource,resourceName:res["resourceName"],resourcePath:res["resourcePath"],isDefined:false})-1; -for(i=0;i<_23.length;i++){ -dojo._xdDepMap[_23[i]]={requires:_21,requiresAfter:_22,contentIndex:_29}; -} -} -for(i=0;i<_23.length;i++){ -dojo._xdInFlight[_23[i]]=false; -} -} -}; -dojo._xdLoadFlattenedBundle=function(_2a,_2b,_2c,_2d){ -_2c=_2c||"root"; -var _2e=dojo.i18n.normalizeLocale(_2c).replace("-","_"); -var _2f=[_2a,"nls",_2b].join("."); -var _30=dojo["provide"](_2f); -_30[_2e]=_2d; -var _31=[_2a,_2e,_2b].join("."); -var _32=dojo._xdBundleMap[_31]; -if(_32){ -for(var _33 in _32){ -_30[_33]=_2d; -} -} -}; -dojo._xdInitExtraLocales=function(){ -var _34=dojo.config.extraLocale; -if(_34){ -if(!_34 instanceof Array){ -_34=[_34]; -} -dojo._xdReqLoc=dojo.xdRequireLocalization; -dojo.xdRequireLocalization=function(m,b,_35,_36){ -dojo._xdReqLoc(m,b,_35,_36); -if(_35){ -return; -} -for(var i=0;i<_34.length;i++){ -dojo._xdReqLoc(m,b,_34[i],_36); -} -}; -} -}; -dojo._xdBundleMap={}; -dojo.xdRequireLocalization=function(_37,_38,_39,_3a){ -if(dojo._xdInitExtraLocales){ -dojo._xdInitExtraLocales(); -dojo._xdInitExtraLocales=null; -dojo.xdRequireLocalization.apply(dojo,arguments); -return; -} -var _3b=_3a.split(","); -var _3c=dojo.i18n.normalizeLocale(_39); -var _3d=""; -for(var i=0;i<_3b.length;i++){ -if(_3c.indexOf(_3b[i])==0){ -if(_3b[i].length>_3d.length){ -_3d=_3b[i]; -} -} -} -var _3e=_3d.replace("-","_"); -var _3f=dojo.getObject([_37,"nls",_38].join(".")); -if(!_3f||!_3f[_3e]){ -var _40=[_37,(_3e||"root"),_38].join("."); -var _41=dojo._xdBundleMap[_40]; -if(!_41){ -_41=dojo._xdBundleMap[_40]={}; -} -_41[_3c.replace("-","_")]=true; -dojo.require(_37+".nls"+(_3d?"."+_3d:"")+"."+_38); -} -}; -dojo._xdRealRequireLocalization=dojo.requireLocalization; -dojo.requireLocalization=function(_42,_43,_44,_45){ -var _46=dojo.moduleUrl(_42).toString(); -if(dojo._xdIsXDomainPath(_46)){ -return dojo.xdRequireLocalization.apply(dojo,arguments); -}else{ -return dojo._xdRealRequireLocalization.apply(dojo,arguments); -} -}; -dojo._xdUnpackDependency=function(dep){ -var _47=null; -var _48=null; -switch(dep[0]){ -case "requireIf": -case "requireAfterIf": -if(dep[1]===true){ -_47=[{name:dep[2],content:null}]; -} -break; -case "platformRequire": -var _49=dep[1]; -var _4a=_49["common"]||[]; -_47=(_49[dojo.hostenv.name_])?_4a.concat(_49[dojo.hostenv.name_]||[]):_4a.concat(_49["default"]||[]); -if(_47){ -for(var i=0;i<_47.length;i++){ -if(_47[i] instanceof Array){ -_47[i]={name:_47[i][0],content:null}; -}else{ -_47[i]={name:_47[i],content:null}; -} -} -} -break; -case "require": -_47=[{name:dep[1],content:null}]; -break; -case "i18n._preloadLocalizations": -dojo.i18n._preloadLocalizations.apply(dojo.i18n._preloadLocalizations,dep.slice(1)); -break; -} -if(dep[0]=="requireAfterIf"||dep[0]=="requireIf"){ -_48=_47; -_47=null; -} -return {requires:_47,requiresAfter:_48}; -}; -dojo._xdWalkReqs=function(){ -var _4b=null; -var req; -for(var i=0;i0){ -var req=_4c[_4c.length-1]; -var res=dojo._xdDepMap[req]; -var i,_4d,_4e; -if(res){ -_4d=res.requires; -if(_4d&&_4d.length>0){ -for(i=0;i<_4d.length;i++){ -_4e=_4d[i].name; -if(_4e&&!_4c[_4e]){ -_4c.push(_4e); -_4c[_4e]=true; -dojo._xdEvalReqs(_4c); -} + +dojo._xdReset = function(){ + //summary: Internal xd loader function. Resets the xd state. + + //This flag indicates where or not we have crossed into xdomain territory. Once any resource says + //it is cross domain, then the rest of the resources have to be treated as xdomain because we need + //to evaluate resources in order. If there is a xdomain resource followed by a xhr resource, we can't load + //the xhr resource until the one before it finishes loading. The text of the xhr resource will be converted + //to match the format for a xd resource and put in the xd load queue. + dojo._isXDomain = dojo.config.useXDomain || false; + + dojo._xdClearInterval(); + dojo._xdInFlight = {}; + dojo._xdOrderedReqs = []; + dojo._xdDepMap = {}; + dojo._xdContents = []; + dojo._xdDefList = []; } + +dojo._xdClearInterval = function(){ + //summary: Internal xd loader function. + //Clears the interval timer used to check on the + //status of in-flight xd module resource requests. + if(dojo._xdTimer){ + clearInterval(dojo._xdTimer); + dojo._xdTimer = 0; + } } -var _4f=dojo._xdContents[res.contentIndex]; -if(!_4f.isDefined){ -var _50=_4f.content; -_50["resourceName"]=_4f["resourceName"]; -_50["resourcePath"]=_4f["resourcePath"]; -dojo._xdDefList.push(_50); -_4f.isDefined=true; + + +//Call reset immediately to set the state. +dojo._xdReset(); + +dojo._xdCreateResource = function(/*String*/contents, /*String*/resourceName, /*String*/resourcePath){ + //summary: Internal xd loader function. Creates an xd module source given an + //non-xd module contents. + + //Remove comments. Not perfect, but good enough for dependency resolution. + var depContents = contents.replace(/(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg , ""); + + //Find dependencies. + var deps = []; + var depRegExp = /dojo.(require|requireIf|provide|requireAfterIf|platformRequire|requireLocalization)\s*\(([\w\W]*?)\)/mg; + var match; + while((match = depRegExp.exec(depContents)) != null){ + if(match[1] == "requireLocalization"){ + //Need to load the local bundles asap, since they are not + //part of the list of modules watched for loading. + eval(match[0]); + }else{ + deps.push('"' + match[1] + '", ' + match[2]); + } + } + + //Create resource object and the call to _xdResourceLoaded. + var output = []; + output.push(dojo._scopeName + "._xdResourceLoaded(function(" + dojo._scopePrefixArgs + "){\n"); + + //See if there are any dojo.loadInit calls + var loadInitCalls = dojo._xdExtractLoadInits(contents); + if(loadInitCalls){ + //Adjust fileContents since extractLoadInits removed something. + contents = loadInitCalls[0]; + + //Add any loadInit calls to the top of the xd file. + for(var i = 1; i < loadInitCalls.length; i++){ + output.push(loadInitCalls[i] + ";\n"); + } + } + + output.push("return {"); + + //Add dependencies + if(deps.length > 0){ + output.push("depends: ["); + for(i = 0; i < deps.length; i++){ + if(i > 0){ + output.push(",\n"); + } + output.push("[" + deps[i] + "]"); + } + output.push("],"); + } + + //Add the contents of the file inside a function. + //Pass in scope arguments so we can support multiple versions of the + //same module on a page. + output.push("\ndefineResource: function(" + dojo._scopePrefixArgs + "){"); + + //Don't put in the contents in the debugAtAllCosts case + //since the contents may have syntax errors. Let those + //get pushed up when the script tags are added to the page + //in the debugAtAllCosts case. + if(!dojo.config["debugAtAllCosts"] || resourceName == "dojo._base._loader.loader_debug"){ + output.push(contents); + } + //Add isLocal property so we know if we have to do something different + //in debugAtAllCosts situations. + output.push("\n}, resourceName: '" + resourceName + "', resourcePath: '" + resourcePath + "'};});"); + + return output.join(""); //String } -dojo._xdDepMap[req]=null; -_4d=res.requiresAfter; -if(_4d&&_4d.length>0){ -for(i=0;i<_4d.length;i++){ -_4e=_4d[i].name; -if(_4e&&!_4c[_4e]){ -_4c.push(_4e); -_4c[_4e]=true; -dojo._xdEvalReqs(_4c); + +dojo._xdExtractLoadInits = function(/*String*/fileContents){ + //Extracts + var regexp = /dojo.loadInit\s*\(/g; + regexp.lastIndex = 0; + + var parenRe = /[\(\)]/g; + parenRe.lastIndex = 0; + + var results = []; + var matches; + while((matches = regexp.exec(fileContents))){ + //Find end of the call by finding the matching end paren + parenRe.lastIndex = regexp.lastIndex; + var matchCount = 1; + var parenMatch; + while((parenMatch = parenRe.exec(fileContents))){ + if(parenMatch[0] == ")"){ + matchCount -= 1; + }else{ + matchCount += 1; + } + if(matchCount == 0){ + break; + } + } + + if(matchCount != 0){ + throw "unmatched paren around character " + parenRe.lastIndex + " in: " + fileContents; + } + + //Put the master matching string in the results. + var startIndex = regexp.lastIndex - matches[0].length; + results.push(fileContents.substring(startIndex, parenRe.lastIndex)); + + //Remove the matching section. + var remLength = parenRe.lastIndex - startIndex; + fileContents = fileContents.substring(0, startIndex) + fileContents.substring(parenRe.lastIndex, fileContents.length); + + //Move the master regexp past the last matching paren point. + regexp.lastIndex = parenRe.lastIndex - remLength; + + regexp.lastIndex = parenRe.lastIndex; + } + + if(results.length > 0){ + results.unshift(fileContents); + } + + return (results.length ? results : null); } + +dojo._xdIsXDomainPath = function(/*string*/relpath) { + //summary: Figure out whether the path is local or x-domain + //If there is a colon before the first / then, we have a URL with a protocol. + + var colonIndex = relpath.indexOf(":"); + var slashIndex = relpath.indexOf("/"); + + if(colonIndex > 0 && colonIndex < slashIndex){ + return true; + }else{ + //Is the base script URI-based URL a cross domain URL? + //If so, then the relpath will be evaluated relative to + //baseUrl, and therefore qualify as xdomain. + //Only treat it as xdomain if the page does not have a + //host (file:// url) or if the baseUrl does not match the + //current window's domain. + var url = dojo.baseUrl; + colonIndex = url.indexOf(":"); + slashIndex = url.indexOf("/"); + if(colonIndex > 0 && colonIndex < slashIndex && (!location.host || url.indexOf("http://" + location.host) != 0)){ + return true; + } + } + return false; } + +dojo._loadPath = function(/*String*/relpath, /*String?*/module, /*Function?*/cb){ + //summary: Internal xd loader function. Overrides loadPath() from loader.js. + //xd loading requires slightly different behavior from loadPath(). + + var currentIsXDomain = dojo._xdIsXDomainPath(relpath); + dojo._isXDomain |= currentIsXDomain; + + var uri = ((relpath.charAt(0) == '/' || relpath.match(/^\w+:/)) ? "" : dojo.baseUrl) + relpath; + + try{ + return ((!module || dojo._isXDomain) ? dojo._loadUri(uri, cb, currentIsXDomain, module) : dojo._loadUriAndCheck(uri, module, cb)); //Boolean + }catch(e){ + console.error(e); + return false; //Boolean + } } + +dojo._xdCharSet = "utf-8"; + +dojo._loadUri = function(/*String*/uri, /*Function?*/cb, /*boolean*/currentIsXDomain, /*String?*/module){ + //summary: Internal xd loader function. Overrides loadUri() from loader.js. + // xd loading requires slightly different behavior from loadPath(). + //description: Wanted to override getText(), but it is used by + // the widget code in too many, synchronous ways right now. + if(dojo._loadedUrls[uri]){ + return 1; //Boolean + } + + //Add the module (resource) to the list of modules. + //Only do this work if we have a modlue name. Otherwise, + //it is a non-xd i18n bundle, which can load immediately and does not + //need to be tracked. Also, don't track dojo.i18n, since it is a prerequisite + //and will be loaded correctly if we load it right away: it has no dependencies. + if(dojo._isXDomain && module && module != "dojo.i18n"){ + dojo._xdOrderedReqs.push(module); + + //Add to waiting resources if it is an xdomain resource. + //Don't add non-xdomain i18n bundles, those get evaled immediately. + if(currentIsXDomain || uri.indexOf("/nls/") == -1){ + dojo._xdInFlight[module] = true; + + //Increment inFlightCount + //This will stop the modulesLoaded from firing all the way. + dojo._inFlightCount++; + } + + //Start timer + if(!dojo._xdTimer){ + if(dojo.isAIR){ + dojo._xdTimer = setInterval(function(){dojo._xdWatchInFlight();}, 100); + }else{ + dojo._xdTimer = setInterval(dojo._scopeName + "._xdWatchInFlight();", 100); + } + } + dojo._xdStartTime = (new Date()).getTime(); + } + + if (currentIsXDomain){ + //Fix name to be a .xd.fileextension name. + var lastIndex = uri.lastIndexOf('.'); + if(lastIndex <= 0){ + lastIndex = uri.length - 1; + } + + var xdUri = uri.substring(0, lastIndex) + ".xd"; + if(lastIndex != uri.length - 1){ + xdUri += uri.substring(lastIndex, uri.length); + } + + if (dojo.isAIR){ + xdUri = xdUri.replace("app:/", "/"); + } + + //Add to script src + var element = document.createElement("script"); + element.type = "text/javascript"; + if(dojo._xdCharSet){ + element.charset = dojo._xdCharSet; + } + element.src = xdUri; + if(!dojo.headElement){ + dojo._headElement = document.getElementsByTagName("head")[0]; + + //Head element may not exist, particularly in html + //html 4 or tag soup cases where the page does not + //have a head tag in it. Use html element, since that will exist. + //Seems to be an issue mostly with Opera 9 and to lesser extent Safari 2 + if(!dojo._headElement){ + dojo._headElement = document.getElementsByTagName("html")[0]; + } + } + dojo._headElement.appendChild(element); + }else{ + var contents = dojo._getText(uri, null, true); + if(contents == null){ return 0; /*boolean*/} + + //If this is not xdomain, or if loading a i18n resource bundle, then send it down + //the normal eval/callback path. + if(dojo._isXDomain + && uri.indexOf("/nls/") == -1 + && module != "dojo.i18n"){ + var res = dojo._xdCreateResource(contents, module, uri); + dojo.eval(res); + }else{ + if(cb){ + contents = '('+contents+')'; + }else{ + //Only do the scoping if no callback. If a callback is specified, + //it is most likely the i18n bundle stuff. + contents = dojo._scopePrefix + contents + dojo._scopeSuffix; + } + var value = dojo["eval"](contents+"\r\n//@ sourceURL="+uri); + if(cb){ + cb(value); + } + } + } + + //These steps are done in the non-xd loader version of this function. + //Maintain these steps to fit in with the existing system. + dojo._loadedUrls[uri] = true; + dojo._loadedUrls.push(uri); + return true; //Boolean } -_4c.pop(); + +dojo._xdResourceLoaded = function(/*Object*/res){ + //summary: Internal xd loader function. Called by an xd module resource when + //it has been loaded via a script tag. + + //Evaluate the function with scopeArgs for multiversion support. + res = res.apply(dojo.global, dojo._scopeArgs); + + //Work through dependencies. + var deps = res.depends; + var requireList = null; + var requireAfterList = null; + var provideList = []; + if(deps && deps.length > 0){ + var dep = null; + var insertHint = 0; + var attachedResource = false; + for(var i = 0; i < deps.length; i++){ + dep = deps[i]; + + //Look for specific dependency indicators. + if (dep[0] == "provide"){ + provideList.push(dep[1]); + }else{ + if(!requireList){ + requireList = []; + } + if(!requireAfterList){ + requireAfterList = []; + } + + var unpackedDeps = dojo._xdUnpackDependency(dep); + if(unpackedDeps.requires){ + requireList = requireList.concat(unpackedDeps.requires); + } + if(unpackedDeps.requiresAfter){ + requireAfterList = requireAfterList.concat(unpackedDeps.requiresAfter); + } + } + + //Call the dependency indicator to allow for the normal dojo setup. + //Only allow for one dot reference, for the i18n._preloadLocalizations calls + //(and maybe future, one-dot things). + var depType = dep[0]; + var objPath = depType.split("."); + if(objPath.length == 2){ + dojo[objPath[0]][objPath[1]].apply(dojo[objPath[0]], dep.slice(1)); + }else{ + dojo[depType].apply(dojo, dep.slice(1)); + } + } + + + //If loading the debugAtAllCosts module, eval it right away since we need + //its functions to properly load the other modules. + if(provideList.length == 1 && provideList[0] == "dojo._base._loader.loader_debug"){ + res.defineResource(dojo); + }else{ + //Save off the resource contents for definition later. + var contentIndex = dojo._xdContents.push({ + content: res.defineResource, + resourceName: res["resourceName"], + resourcePath: res["resourcePath"], + isDefined: false + }) - 1; + + //Add provide/requires to dependency map. + for(i = 0; i < provideList.length; i++){ + dojo._xdDepMap[provideList[i]] = { requires: requireList, requiresAfter: requireAfterList, contentIndex: contentIndex }; + } + } + + //Now update the inflight status for any provided resources in this loaded resource. + //Do this at the very end (in a *separate* for loop) to avoid shutting down the + //inflight timer check too soon. + for(i = 0; i < provideList.length; i++){ + dojo._xdInFlight[provideList[i]] = false; + } + } } + +dojo._xdLoadFlattenedBundle = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*Object*/bundleData){ + //summary: Internal xd loader function. Used when loading + //a flattened localized bundle via a script tag. + locale = locale || "root"; + var jsLoc = dojo.i18n.normalizeLocale(locale).replace('-', '_'); + var bundleResource = [moduleName, "nls", bundleName].join("."); + var bundle = dojo["provide"](bundleResource); + bundle[jsLoc] = bundleData; + + //Assign the bundle for the original locale(s) we wanted. + var mapName = [moduleName, jsLoc, bundleName].join("."); + var bundleMap = dojo._xdBundleMap[mapName]; + if(bundleMap){ + for(var param in bundleMap){ + bundle[param] = bundleData; + } + } }; -dojo._xdWatchInFlight=function(){ -var _51=""; -var _52=(dojo.config.xdWaitSeconds||15)*1000; -var _53=(dojo._xdStartTime+_52)<(new Date()).getTime(); -for(var _54 in dojo._xdInFlight){ -if(dojo._xdInFlight[_54]===true){ -if(_53){ -_51+=_54+" "; -}else{ -return; -} -} -} -dojo._xdClearInterval(); -if(_53){ -throw "Could not load cross-domain resources: "+_51; -} -dojo._xdWalkReqs(); -var _55=dojo._xdDefList.length; -for(var i=0;i<_55;i++){ -var _56=dojo._xdDefList[i]; -if(dojo.config["debugAtAllCosts"]&&_56["resourceName"]){ -if(!dojo["_xdDebugQueue"]){ -dojo._xdDebugQueue=[]; -} -dojo._xdDebugQueue.push({resourceName:_56.resourceName,resourcePath:_56.resourcePath}); -}else{ -_56.apply(dojo.global,dojo._scopeArgs); + + +dojo._xdInitExtraLocales = function(){ + // Simulate the extra locale work that dojo.requireLocalization does. + + var extra = dojo.config.extraLocale; + if(extra){ + if(!extra instanceof Array){ + extra = [extra]; + } + + dojo._xdReqLoc = dojo.xdRequireLocalization; + dojo.xdRequireLocalization = function(m, b, locale, fLocales){ + dojo._xdReqLoc(m,b,locale, fLocales); + if(locale){return;} + for(var i=0; i bestLocale.length){ + bestLocale = locales[i]; + } + } + } + + var fixedBestLocale = bestLocale.replace('-', '_'); + //See if the bundle we are going to use is already loaded. + var bundleResource = dojo.getObject([moduleName, "nls", bundleName].join(".")); + if(!bundleResource || !bundleResource[fixedBestLocale]){ + //Need to remember what locale we wanted and which one we actually use. + //Then when we load the one we are actually using, use that bundle for the one + //we originally wanted. + var mapName = [moduleName, (fixedBestLocale||"root"), bundleName].join("."); + var bundleMap = dojo._xdBundleMap[mapName]; + if(!bundleMap){ + bundleMap = dojo._xdBundleMap[mapName] = {}; + } + bundleMap[jsLoc.replace('-', '_')] = true; + + //Do just a normal dojo.require so the resource tracking stuff works as usual. + dojo.require(moduleName + ".nls" + (bestLocale ? "." + bestLocale : "") + "." + bundleName); + } } -for(i=0;i0){ -dojo._xdDebugFileLoaded(); -}else{ -dojo._xdNotifyLoaded(); + +dojo._xdWalkReqs = function(){ + //summary: Internal xd loader function. + //Walks the requires and evaluates module resource contents in + //the right order. + var reqChain = null; + var req; + for(var i = 0; i < dojo._xdOrderedReqs.length; i++){ + req = dojo._xdOrderedReqs[i]; + if(dojo._xdDepMap[req]){ + reqChain = [req]; + reqChain[req] = true; //Allow for fast lookup of the req in the array + dojo._xdEvalReqs(reqChain); + } + } } -}; -dojo._xdNotifyLoaded=function(){ -for(var _58 in dojo._xdInFlight){ -if(typeof dojo._xdInFlight[_58]=="boolean"){ -return; + +dojo._xdEvalReqs = function(/*Array*/reqChain){ + //summary: Internal xd loader function. + //Does a depth first, breadth second search and eval of required modules. + while(reqChain.length > 0){ + var req = reqChain[reqChain.length - 1]; + var res = dojo._xdDepMap[req]; + var i, reqs, nextReq; + if(res){ + //Trace down any requires for this resource. + //START dojo._xdTraceReqs() inlining for small Safari 2.0 call stack + reqs = res.requires; + if(reqs && reqs.length > 0){ + for(i = 0; i < reqs.length; i++){ + nextReq = reqs[i].name; + if(nextReq && !reqChain[nextReq]){ + //New req depedency. Follow it down. + reqChain.push(nextReq); + reqChain[nextReq] = true; + dojo._xdEvalReqs(reqChain); + } + } + } + //END dojo._xdTraceReqs() inlining for small Safari 2.0 call stack + + //Evaluate the resource. + var contents = dojo._xdContents[res.contentIndex]; + if(!contents.isDefined){ + var content = contents.content; + content["resourceName"] = contents["resourceName"]; + content["resourcePath"] = contents["resourcePath"]; + dojo._xdDefList.push(content); + contents.isDefined = true; + } + dojo._xdDepMap[req] = null; + + //Trace down any requireAfters for this resource. + //START dojo._xdTraceReqs() inlining for small Safari 2.0 call stack + reqs = res.requiresAfter; + if(reqs && reqs.length > 0){ + for(i = 0; i < reqs.length; i++){ + nextReq = reqs[i].name; + if(nextReq && !reqChain[nextReq]){ + //New req depedency. Follow it down. + reqChain.push(nextReq); + reqChain[nextReq] = true; + dojo._xdEvalReqs(reqChain); + } + } + } + //END dojo._xdTraceReqs() inlining for small Safari 2.0 call stack + } + + //Done with that require. Remove it and go to the next one. + reqChain.pop(); + } } + +dojo._xdWatchInFlight = function(){ + //summary: Internal xd loader function. + //Monitors in-flight requests for xd module resources. + + var noLoads = ""; + var waitInterval = (dojo.config.xdWaitSeconds || 15) * 1000; + var expired = (dojo._xdStartTime + waitInterval) < (new Date()).getTime(); + + //If any xdInFlight are true, then still waiting for something to load. + //Come back later. If we timed out, report the things that did not load. + for(var param in dojo._xdInFlight){ + if(dojo._xdInFlight[param] === true){ + if(expired){ + noLoads += param + " "; + }else{ + return; + } + } + } + + //All done. Clean up and notify. + dojo._xdClearInterval(); + + if(expired){ + throw "Could not load cross-domain resources: " + noLoads; + } + + dojo._xdWalkReqs(); + + var defLength = dojo._xdDefList.length; + for(var i= 0; i < defLength; i++){ + var content = dojo._xdDefList[i]; + if(dojo.config["debugAtAllCosts"] && content["resourceName"]){ + if(!dojo["_xdDebugQueue"]){ + dojo._xdDebugQueue = []; + } + dojo._xdDebugQueue.push({resourceName: content.resourceName, resourcePath: content.resourcePath}); + }else{ + //Evaluate the resource to bring it into being. + //Pass in scope args to allow multiple versions of modules in a page. + content.apply(dojo.global, dojo._scopeArgs); + } + } + + //Evaluate any resources that were not evaled before. + //This normally shouldn't happen with proper dojo.provide and dojo.require + //usage, but providing it just in case. Note that these may not be executed + //in the original order that the developer intended. + for(i = 0; i < dojo._xdContents.length; i++){ + var current = dojo._xdContents[i]; + if(current.content && !current.isDefined){ + //Pass in scope args to allow multiple versions of modules in a page. + current.content.apply(dojo.global, dojo._scopeArgs); + } + } + + //Clean up for the next round of xd loading. + dojo._xdReset(); + + if(dojo["_xdDebugQueue"] && dojo._xdDebugQueue.length > 0){ + dojo._xdDebugFileLoaded(); + }else{ + dojo._xdNotifyLoaded(); + } } -dojo._inFlightCount=0; -if(dojo._initFired&&!dojo._loadNotifying){ -dojo._callLoaded(); + +dojo._xdNotifyLoaded = function(){ + //Clear inflight count so we will finally do finish work. + + //Just having a legitimate status (true or false) for an inflight item + //means that it is still being processed. Do the typeof test + //to avoid bad JavaScript that might tinker with Object.prototype. + for(var prop in dojo._xdInFlight){ + if(typeof dojo._xdInFlight[prop] == "boolean"){ + return; + } + } + + dojo._inFlightCount = 0; + + //Only trigger call loaded if dj_load_init has run. + if(dojo._initFired && !dojo._loadNotifying){ + dojo._callLoaded(); + } } -}; + } diff --git a/lib/dojo/_base/array.js b/lib/dojo/_base/array.js index 83c21c8d..26fa1900 100644 --- a/lib/dojo/_base/array.js +++ b/lib/dojo/_base/array.js @@ -5,75 +5,258 @@ */ -if(!dojo._hasResource["dojo._base.array"]){ -dojo._hasResource["dojo._base.array"]=true; +if(!dojo._hasResource["dojo._base.array"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.array"] = true; dojo.require("dojo._base.lang"); dojo.provide("dojo._base.array"); + (function(){ -var _1=function(_2,_3,cb){ -return [(typeof _2=="string")?_2.split(""):_2,_3||dojo.global,(typeof cb=="string")?new Function("item","index","array",cb):cb]; -}; -var _4=function(_5,_6,_7,_8){ -var _9=_1(_6,_8,_7); -_6=_9[0]; -for(var i=0,l=_6.length;iend)||i end) || i < end){ + for(; i != end; i += step){ + if(array[i] == value){ return i; } + } + } + return -1; // Number + }, + + lastIndexOf: function(/*Array*/array, /*Object*/value, /*Integer?*/fromIndex){ + // summary: + // locates the last index of the provided value in the passed + // array. If the value is not found, -1 is returned. + // description: + // This method corresponds to the JavaScript 1.6 Array.lastIndexOf method, with one difference: when + // run over sparse arrays, the Dojo function invokes the callback for every index whereas JavaScript + // 1.6's lastIndexOf skips the holes in the sparse array. + // For details on this method, see: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/lastIndexOf + return dojo.indexOf(array, value, fromIndex, true); // Number + }, + + forEach: function(/*Array|String*/arr, /*Function|String*/callback, /*Object?*/thisObject){ + // summary: + // for every item in arr, callback is invoked. Return values are ignored. + // If you want to break out of the loop, consider using dojo.every() or dojo.some(). + // forEach does not allow breaking out of the loop over the items in arr. + // arr: + // the array to iterate over. If a string, operates on individual characters. + // callback: + // a function is invoked with three arguments: item, index, and array + // thisObject: + // may be used to scope the call to callback + // description: + // This function corresponds to the JavaScript 1.6 Array.forEach() method, with one difference: when + // run over sparse arrays, this implemenation passes the "holes" in the sparse array to + // the callback function with a value of undefined. JavaScript 1.6's forEach skips the holes in the sparse array. + // For more details, see: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/forEach + // example: + // | // log out all members of the array: + // | dojo.forEach( + // | [ "thinger", "blah", "howdy", 10 ], + // | function(item){ + // | console.log(item); + // | } + // | ); + // example: + // | // log out the members and their indexes + // | dojo.forEach( + // | [ "thinger", "blah", "howdy", 10 ], + // | function(item, idx, arr){ + // | console.log(item, "at index:", idx); + // | } + // | ); + // example: + // | // use a scoped object member as the callback + // | + // | var obj = { + // | prefix: "logged via obj.callback:", + // | callback: function(item){ + // | console.log(this.prefix, item); + // | } + // | }; + // | + // | // specifying the scope function executes the callback in that scope + // | dojo.forEach( + // | [ "thinger", "blah", "howdy", 10 ], + // | obj.callback, + // | obj + // | ); + // | + // | // alternately, we can accomplish the same thing with dojo.hitch() + // | dojo.forEach( + // | [ "thinger", "blah", "howdy", 10 ], + // | dojo.hitch(obj, "callback") + // | ); + + // match the behavior of the built-in forEach WRT empty arrs + if(!arr || !arr.length){ return; } + + // FIXME: there are several ways of handilng thisObject. Is + // dojo.global always the default context? + var _p = _getParts(arr, thisObject, callback); arr = _p[0]; + for(var i=0,l=arr.length; i1; }); + // example: + // | // returns true + // | dojo.every([1, 2, 3, 4], function(item){ return item>0; }); + return everyOrSome(true, arr, callback, thisObject); // Boolean + }, + + some: function(/*Array|String*/arr, /*Function|String*/callback, /*Object?*/thisObject){ + // summary: + // Determines whether or not any item in arr satisfies the + // condition implemented by callback. + // arr: + // the array to iterate over. If a string, operates on individual characters. + // callback: + // a function is invoked with three arguments: item, index, + // and array and returns true if the condition is met. + // thisObject: + // may be used to scope the call to callback + // description: + // This function corresponds to the JavaScript 1.6 Array.some() method, with one difference: when + // run over sparse arrays, this implemenation passes the "holes" in the sparse array to + // the callback function with a value of undefined. JavaScript 1.6's some skips the holes in the sparse array. + // For more details, see: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/some + // example: + // | // is true + // | dojo.some([1, 2, 3, 4], function(item){ return item>1; }); + // example: + // | // is false + // | dojo.some([1, 2, 3, 4], function(item){ return item<1; }); + return everyOrSome(false, arr, callback, thisObject); // Boolean + }, + + map: function(/*Array|String*/arr, /*Function|String*/callback, /*Function?*/thisObject){ + // summary: + // applies callback to each element of arr and returns + // an Array with the results + // arr: + // the array to iterate on. If a string, operates on + // individual characters. + // callback: + // a function is invoked with three arguments, (item, index, + // array), and returns a value + // thisObject: + // may be used to scope the call to callback + // description: + // This function corresponds to the JavaScript 1.6 Array.map() method, with one difference: when + // run over sparse arrays, this implemenation passes the "holes" in the sparse array to + // the callback function with a value of undefined. JavaScript 1.6's map skips the holes in the sparse array. + // For more details, see: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map + // example: + // | // returns [2, 3, 4, 5] + // | dojo.map([1, 2, 3, 4], function(item){ return item+1 }); + + var _p = _getParts(arr, thisObject, callback); arr = _p[0]; + var outArr = (arguments[3] ? (new arguments[3]()) : []); + for(var i=0,l=arr.length; i1; }); + + var _p = _getParts(arr, thisObject, callback); arr = _p[0]; + var outArr = []; + for(var i=0,l=arr.length; i=0;--j){ -_10=_e[j].prototype; -if(!_10.hasOwnProperty("declaredClass")){ -_10.declaredClass="uniqName_"+(_4++); -} -_11=_10.declaredClass; -if(!_c.hasOwnProperty(_11)){ -_c[_11]={count:0,refs:[],cls:_e[j]}; -++_d; -} -rec=_c[_11]; -if(top&&top!==rec){ -rec.refs.push(top); -++top.count; -} -top=rec; -} -++top.count; -_b[0].refs.push(top); -} -while(_b.length){ -top=_b.pop(); -_a.push(top.cls); ---_d; -while(_12=top.refs,_12.length==1){ -top=_12[0]; -if(!top||--top.count){ -top=0; -break; -} -_a.push(top.cls); ---_d; -} -if(top){ -for(i=0,l=_12.length;i=0;--i){ -f=_2d[i]; -m=f._meta; -f=m?m.ctor:f; -if(f){ -f.apply(this,_30?_30[i]:a); -} -} -f=this.postscript; -if(f){ -f.apply(this,_2f); -} -}; -}; -function _32(_33,_34){ -return function(){ -var a=arguments,t=a,a0=a[0],f; -if(!(this instanceof a.callee)){ -return _31(a); -} -if(_34){ -if(a0){ -f=a0.preamble; -if(f){ -t=f.apply(this,t)||t; -} -} -f=this.preamble; -if(f){ -f.apply(this,t); -} -} -if(_33){ -_33.apply(this,a); -} -f=this.postscript; -if(f){ -f.apply(this,a); -} -}; -}; -function _35(_36){ -return function(){ -var a=arguments,i=0,f,m; -if(!(this instanceof a.callee)){ -return _31(a); -} -for(;f=_36[i];++i){ -m=f._meta; -f=m?m.ctor:f; -if(f){ -f.apply(this,a); -break; -} -} -f=this.postscript; -if(f){ -f.apply(this,a); -} -}; -}; -function _37(_38,_39,_3a){ -return function(){ -var b,m,f,i=0,_3b=1; -if(_3a){ -i=_39.length-1; -_3b=-1; -} -for(;b=_39[i];i+=_3b){ -m=b._meta; -f=(m?m.hidden:b.prototype)[_38]; -if(f){ -f.apply(this,arguments); -} -} -}; -}; -function _3c(_3d){ -_3.prototype=_3d.prototype; -var t=new _3; -_3.prototype=null; -return t; -}; -function _31(_3e){ -var _3f=_3e.callee,t=_3c(_3f); -_3f.apply(t,_3e); -return t; -}; -d.declare=function(_40,_41,_42){ -if(typeof _40!="string"){ -_42=_41; -_41=_40; -_40=""; -} -_42=_42||{}; -var _43,i,t,_44,_45,_46,_47,_48=1,_49=_41; -if(_2.call(_41)=="[object Array]"){ -_46=_8(_41); -t=_46[0]; -_48=_46.length-t; -_41=_46[_48]; -}else{ -_46=[0]; -if(_41){ -if(_2.call(_41)=="[object Function]"){ -t=_41._meta; -_46=_46.concat(t?t.bases:_41); -}else{ -_6("base class is not a callable constructor."); -} -}else{ -if(_41!==null){ -_6("unknown base class. Did you use dojo.require to pull it in?"); -} -} -} -if(_41){ -for(i=_48-1;;--i){ -_43=_3c(_41); -if(!i){ -break; -} -t=_46[i]; -(t._meta?_22:_1)(_43,t.prototype); -_44=new Function; -_44.superclass=_41; -_44.prototype=_43; -_41=_43.constructor=_44; -} -}else{ -_43={}; -} -_26(_43,_42); -t=_42.constructor; -if(t!==op.constructor){ -t.nom=_5; -_43.constructor=t; -} -for(i=_48-1;i;--i){ -t=_46[i]._meta; -if(t&&t.chains){ -_47=_1(_47||{},t.chains); -} -} -if(_43["-chains-"]){ -_47=_1(_47||{},_43["-chains-"]); -} -t=!_47||!_47.hasOwnProperty(_5); -_46[0]=_44=(_47&&_47.constructor==="manual")?_35(_46):(_46.length==1?_32(_42.constructor,t):_2c(_46,t)); -_44._meta={bases:_46,hidden:_42,chains:_47,parents:_49,ctor:_42.constructor}; -_44.superclass=_41&&_41.prototype; -_44.extend=_2a; -_44.prototype=_43; -_43.constructor=_44; -_43.getInherited=_1d; -_43.inherited=_13; -_43.isInstanceOf=_20; -if(_40){ -_43.declaredClass=_40; -d.setObject(_40,_44); -} -if(_47){ -for(_45 in _47){ -if(_43[_45]&&typeof _47[_45]=="string"&&_45!=_5){ -t=_43[_45]=_37(_45,_46,_47[_45]==="after"); -t.nom=_45; -} -} -} -return _44; -}; -d.safeMixin=_26; + var d = dojo, mix = d._mixin, op = Object.prototype, opts = op.toString, + xtor = new Function, counter = 0, cname = "constructor"; + + function err(msg){ throw new Error("declare: " + msg); } + + // C3 Method Resolution Order (see http://www.python.org/download/releases/2.3/mro/) + function c3mro(bases){ + var result = [], roots = [{cls: 0, refs: []}], nameMap = {}, clsCount = 1, + l = bases.length, i = 0, j, lin, base, top, proto, rec, name, refs; + + // build a list of bases naming them if needed + for(; i < l; ++i){ + base = bases[i]; + if(!base){ + err("mixin #" + i + " is unknown. Did you use dojo.require to pull it in?"); + }else if(opts.call(base) != "[object Function]"){ + err("mixin #" + i + " is not a callable constructor."); + } + lin = base._meta ? base._meta.bases : [base]; + top = 0; + // add bases to the name map + for(j = lin.length - 1; j >= 0; --j){ + proto = lin[j].prototype; + if(!proto.hasOwnProperty("declaredClass")){ + proto.declaredClass = "uniqName_" + (counter++); + } + name = proto.declaredClass; + if(!nameMap.hasOwnProperty(name)){ + nameMap[name] = {count: 0, refs: [], cls: lin[j]}; + ++clsCount; + } + rec = nameMap[name]; + if(top && top !== rec){ + rec.refs.push(top); + ++top.count; + } + top = rec; + } + ++top.count; + roots[0].refs.push(top); + } + + // remove classes without external references recursively + while(roots.length){ + top = roots.pop(); + result.push(top.cls); + --clsCount; + // optimization: follow a single-linked chain + while(refs = top.refs, refs.length == 1){ + top = refs[0]; + if(!top || --top.count){ + // branch or end of chain => do not end to roots + top = 0; + break; + } + result.push(top.cls); + --clsCount; + } + if(top){ + // branch + for(i = 0, l = refs.length; i < l; ++i){ + top = refs[i]; + if(!--top.count){ + roots.push(top); + } + } + } + } + if(clsCount){ + err("can't build consistent linearization"); + } + + // calculate the superclass offset + base = bases[0]; + result[0] = base ? + base._meta && base === result[result.length - base._meta.bases.length] ? + base._meta.bases.length : 1 : 0; + + return result; + } + + function inherited(args, a, f){ + var name, chains, bases, caller, meta, base, proto, opf, pos, + cache = this._inherited = this._inherited || {}; + + // crack arguments + if(typeof args == "string"){ + name = args; + args = a; + a = f; + } + f = 0; + + caller = args.callee; + name = name || caller.nom; + if(!name){ + err("can't deduce a name to call inherited()"); + } + + meta = this.constructor._meta; + bases = meta.bases; + + pos = cache.p; + if(name != cname){ + // method + if(cache.c !== caller){ + // cache bust + pos = 0; + base = bases[0]; + meta = base._meta; + if(meta.hidden[name] !== caller){ + // error detection + chains = meta.chains; + if(chains && typeof chains[name] == "string"){ + err("calling chained method with inherited: " + name); + } + // find caller + do{ + meta = base._meta; + proto = base.prototype; + if(meta && (proto[name] === caller && proto.hasOwnProperty(name) || meta.hidden[name] === caller)){ + break; + } + }while(base = bases[++pos]); // intentional assignment + pos = base ? pos : -1; + } + } + // find next + base = bases[++pos]; + if(base){ + proto = base.prototype; + if(base._meta && proto.hasOwnProperty(name)){ + f = proto[name]; + }else{ + opf = op[name]; + do{ + proto = base.prototype; + f = proto[name]; + if(f && (base._meta ? proto.hasOwnProperty(name) : f !== opf)){ + break; + } + }while(base = bases[++pos]); // intentional assignment + } + } + f = base && f || op[name]; + }else{ + // constructor + if(cache.c !== caller){ + // cache bust + pos = 0; + meta = bases[0]._meta; + if(meta && meta.ctor !== caller){ + // error detection + chains = meta.chains; + if(!chains || chains.constructor !== "manual"){ + err("calling chained constructor with inherited"); + } + // find caller + while(base = bases[++pos]){ // intentional assignment + meta = base._meta; + if(meta && meta.ctor === caller){ + break; + } + } + pos = base ? pos : -1; + } + } + // find next + while(base = bases[++pos]){ // intentional assignment + meta = base._meta; + f = meta ? meta.ctor : base; + if(f){ + break; + } + } + f = base && f; + } + + // cache the found super method + cache.c = f; + cache.p = pos; + + // now we have the result + if(f){ + return a === true ? f : f.apply(this, a || args); + } + // intentionally if a super method was not found + } + + function getInherited(name, args){ + if(typeof name == "string"){ + return this.inherited(name, args, true); + } + return this.inherited(name, true); + } + + // emulation of "instanceof" + function isInstanceOf(cls){ + var bases = this.constructor._meta.bases; + for(var i = 0, l = bases.length; i < l; ++i){ + if(bases[i] === cls){ + return true; + } + } + return this instanceof cls; + } + + function mixOwn(target, source){ + var name, i = 0, l = d._extraNames.length; + // add props adding metadata for incoming functions skipping a constructor + for(name in source){ + if(name != cname && source.hasOwnProperty(name)){ + target[name] = source[name]; + } + } + // process unenumerable methods on IE + for(; i < l; ++i){ + name = d._extraNames[i]; + if(name != cname && source.hasOwnProperty(name)){ + target[name] = source[name]; + } + } + } + + // implementation of safe mixin function + function safeMixin(target, source){ + var name, t, i = 0, l = d._extraNames.length; + // add props adding metadata for incoming functions skipping a constructor + for(name in source){ + t = source[name]; + if((t !== op[name] || !(name in op)) && name != cname){ + if(opts.call(t) == "[object Function]"){ + // non-trivial function method => attach its name + t.nom = name; + } + target[name] = t; + } + } + // process unenumerable methods on IE + for(; i < l; ++i){ + name = d._extraNames[i]; + t = source[name]; + if((t !== op[name] || !(name in op)) && name != cname){ + if(opts.call(t) == "[object Function]"){ + // non-trivial function method => attach its name + t.nom = name; + } + target[name] = t; + } + } + return target; + } + + function extend(source){ + safeMixin(this.prototype, source); + return this; + } + + // chained constructor compatible with the legacy dojo.declare() + function chainedConstructor(bases, ctorSpecial){ + return function(){ + var a = arguments, args = a, a0 = a[0], f, i, m, + l = bases.length, preArgs; + + if(!(this instanceof a.callee)){ + // not called via new, so force it + return applyNew(a); + } + + //this._inherited = {}; + // perform the shaman's rituals of the original dojo.declare() + // 1) call two types of the preamble + if(ctorSpecial && (a0 && a0.preamble || this.preamble)){ + // full blown ritual + preArgs = new Array(bases.length); + // prepare parameters + preArgs[0] = a; + for(i = 0;;){ + // process the preamble of the 1st argument + a0 = a[0]; + if(a0){ + f = a0.preamble; + if(f){ + a = f.apply(this, a) || a; + } + } + // process the preamble of this class + f = bases[i].prototype; + f = f.hasOwnProperty("preamble") && f.preamble; + if(f){ + a = f.apply(this, a) || a; + } + // one peculiarity of the preamble: + // it is called if it is not needed, + // e.g., there is no constructor to call + // let's watch for the last constructor + // (see ticket #9795) + if(++i == l){ + break; + } + preArgs[i] = a; + } + } + // 2) call all non-trivial constructors using prepared arguments + for(i = l - 1; i >= 0; --i){ + f = bases[i]; + m = f._meta; + f = m ? m.ctor : f; + if(f){ + f.apply(this, preArgs ? preArgs[i] : a); + } + } + // 3) continue the original ritual: call the postscript + f = this.postscript; + if(f){ + f.apply(this, args); + } + }; + } + + + // chained constructor compatible with the legacy dojo.declare() + function singleConstructor(ctor, ctorSpecial){ + return function(){ + var a = arguments, t = a, a0 = a[0], f; + + if(!(this instanceof a.callee)){ + // not called via new, so force it + return applyNew(a); + } + + //this._inherited = {}; + // perform the shaman's rituals of the original dojo.declare() + // 1) call two types of the preamble + if(ctorSpecial){ + // full blown ritual + if(a0){ + // process the preamble of the 1st argument + f = a0.preamble; + if(f){ + t = f.apply(this, t) || t; + } + } + f = this.preamble; + if(f){ + // process the preamble of this class + f.apply(this, t); + // one peculiarity of the preamble: + // it is called even if it is not needed, + // e.g., there is no constructor to call + // let's watch for the last constructor + // (see ticket #9795) + } + } + // 2) call a constructor + if(ctor){ + ctor.apply(this, a); + } + // 3) continue the original ritual: call the postscript + f = this.postscript; + if(f){ + f.apply(this, a); + } + }; + } + + // plain vanilla constructor (can use inherited() to call its base constructor) + function simpleConstructor(bases){ + return function(){ + var a = arguments, i = 0, f, m; + + if(!(this instanceof a.callee)){ + // not called via new, so force it + return applyNew(a); + } + + //this._inherited = {}; + // perform the shaman's rituals of the original dojo.declare() + // 1) do not call the preamble + // 2) call the top constructor (it can use this.inherited()) + for(; f = bases[i]; ++i){ // intentional assignment + m = f._meta; + f = m ? m.ctor : f; + if(f){ + f.apply(this, a); + break; + } + } + // 3) call the postscript + f = this.postscript; + if(f){ + f.apply(this, a); + } + }; + } + + function chain(name, bases, reversed){ + return function(){ + var b, m, f, i = 0, step = 1; + if(reversed){ + i = bases.length - 1; + step = -1; + } + for(; b = bases[i]; i += step){ // intentional assignment + m = b._meta; + f = (m ? m.hidden : b.prototype)[name]; + if(f){ + f.apply(this, arguments); + } + } + }; + } + + // forceNew(ctor) + // return a new object that inherits from ctor.prototype but + // without actually running ctor on the object. + function forceNew(ctor){ + // create object with correct prototype using a do-nothing + // constructor + xtor.prototype = ctor.prototype; + var t = new xtor; + xtor.prototype = null; // clean up + return t; + } + + // applyNew(args) + // just like 'new ctor()' except that the constructor and its arguments come + // from args, which must be an array or an arguments object + function applyNew(args){ + // create an object with ctor's prototype but without + // calling ctor on it. + var ctor = args.callee, t = forceNew(ctor); + // execute the real constructor on the new object + ctor.apply(t, args); + return t; + } + + d.declare = function(className, superclass, props){ + // crack parameters + if(typeof className != "string"){ + props = superclass; + superclass = className; + className = ""; + } + props = props || {}; + + var proto, i, t, ctor, name, bases, chains, mixins = 1, parents = superclass; + + // build a prototype + if(opts.call(superclass) == "[object Array]"){ + // C3 MRO + bases = c3mro(superclass); + t = bases[0]; + mixins = bases.length - t; + superclass = bases[mixins]; + }else{ + bases = [0]; + if(superclass){ + if(opts.call(superclass) == "[object Function]"){ + t = superclass._meta; + bases = bases.concat(t ? t.bases : superclass); + }else{ + err("base class is not a callable constructor."); + } + }else if(superclass !== null){ + err("unknown base class. Did you use dojo.require to pull it in?") + } + } + if(superclass){ + for(i = mixins - 1;; --i){ + proto = forceNew(superclass); + if(!i){ + // stop if nothing to add (the last base) + break; + } + // mix in properties + t = bases[i]; + (t._meta ? mixOwn : mix)(proto, t.prototype); + // chain in new constructor + ctor = new Function; + ctor.superclass = superclass; + ctor.prototype = proto; + superclass = proto.constructor = ctor; + } + }else{ + proto = {}; + } + // add all properties + safeMixin(proto, props); + // add constructor + t = props.constructor; + if(t !== op.constructor){ + t.nom = cname; + proto.constructor = t; + } + + // collect chains and flags + for(i = mixins - 1; i; --i){ // intentional assignment + t = bases[i]._meta; + if(t && t.chains){ + chains = mix(chains || {}, t.chains); + } + } + if(proto["-chains-"]){ + chains = mix(chains || {}, proto["-chains-"]); + } + + // build ctor + t = !chains || !chains.hasOwnProperty(cname); + bases[0] = ctor = (chains && chains.constructor === "manual") ? simpleConstructor(bases) : + (bases.length == 1 ? singleConstructor(props.constructor, t) : chainedConstructor(bases, t)); + + // add meta information to the constructor + ctor._meta = {bases: bases, hidden: props, chains: chains, + parents: parents, ctor: props.constructor}; + ctor.superclass = superclass && superclass.prototype; + ctor.extend = extend; + ctor.prototype = proto; + proto.constructor = ctor; + + // add "standard" methods to the prototype + proto.getInherited = getInherited; + proto.inherited = inherited; + proto.isInstanceOf = isInstanceOf; + + // add name if specified + if(className){ + proto.declaredClass = className; + d.setObject(className, ctor); + } + + // build chains and add them to the prototype + if(chains){ + for(name in chains){ + if(proto[name] && typeof chains[name] == "string" && name != cname){ + t = proto[name] = chain(name, bases, chains[name] === "after"); + t.nom = name; + } + } + } + // chained methods do not return values + // no need to chain "invisible" functions + + return ctor; // Function + }; + + d.safeMixin = safeMixin; + + /*===== + dojo.declare = function(className, superclass, props){ + // summary: + // Create a feature-rich constructor from compact notation. + // className: String?: + // The optional name of the constructor (loosely, a "class") + // stored in the "declaredClass" property in the created prototype. + // It will be used as a global name for a created constructor. + // superclass: Function|Function[]: + // May be null, a Function, or an Array of Functions. This argument + // specifies a list of bases (the left-most one is the most deepest + // base). + // props: Object: + // An object whose properties are copied to the created prototype. + // Add an instance-initialization function by making it a property + // named "constructor". + // returns: + // New constructor function. + // description: + // Create a constructor using a compact notation for inheritance and + // prototype extension. + // + // Mixin ancestors provide a type of multiple inheritance. + // Prototypes of mixin ancestors are copied to the new class: + // changes to mixin prototypes will not affect classes to which + // they have been mixed in. + // + // Ancestors can be compound classes created by this version of + // dojo.declare. In complex cases all base classes are going to be + // linearized according to C3 MRO algorithm + // (see http://www.python.org/download/releases/2.3/mro/ for more + // details). + // + // "className" is cached in "declaredClass" property of the new class, + // if it was supplied. The immediate super class will be cached in + // "superclass" property of the new class. + // + // Methods in "props" will be copied and modified: "nom" property + // (the declared name of the method) will be added to all copied + // functions to help identify them for the internal machinery. Be + // very careful, while reusing methods: if you use the same + // function under different names, it can produce errors in some + // cases. + // + // It is possible to use constructors created "manually" (without + // dojo.declare) as bases. They will be called as usual during the + // creation of an instance, their methods will be chained, and even + // called by "this.inherited()". + // + // Special property "-chains-" governs how to chain methods. It is + // a dictionary, which uses method names as keys, and hint strings + // as values. If a hint string is "after", this method will be + // called after methods of its base classes. If a hint string is + // "before", this method will be called before methods of its base + // classes. + // + // If "constructor" is not mentioned in "-chains-" property, it will + // be chained using the legacy mode: using "after" chaining, + // calling preamble() method before each constructor, if available, + // and calling postscript() after all constructors were executed. + // If the hint is "after", it is chained as a regular method, but + // postscript() will be called after the chain of constructors. + // "constructor" cannot be chained "before", but it allows + // a special hint string: "manual", which means that constructors + // are not going to be chained in any way, and programmer will call + // them manually using this.inherited(). In the latter case + // postscript() will be called after the construction. + // + // All chaining hints are "inherited" from base classes and + // potentially can be overridden. Be very careful when overriding + // hints! Make sure that all chained methods can work in a proposed + // manner of chaining. + // + // Once a method was chained, it is impossible to unchain it. The + // only exception is "constructor". You don't need to define a + // method in order to supply a chaining hint. + // + // If a method is chained, it cannot use this.inherited() because + // all other methods in the hierarchy will be called automatically. + // + // Usually constructors and initializers of any kind are chained + // using "after" and destructors of any kind are chained as + // "before". Note that chaining assumes that chained methods do not + // return any value: any returned value will be discarded. + // + // example: + // | dojo.declare("my.classes.bar", my.classes.foo, { + // | // properties to be added to the class prototype + // | someValue: 2, + // | // initialization function + // | constructor: function(){ + // | this.myComplicatedObject = new ReallyComplicatedObject(); + // | }, + // | // other functions + // | someMethod: function(){ + // | doStuff(); + // | } + // | }); + // + // example: + // | var MyBase = dojo.declare(null, { + // | // constructor, properties, and methods go here + // | // ... + // | }); + // | var MyClass1 = dojo.declare(MyBase, { + // | // constructor, properties, and methods go here + // | // ... + // | }); + // | var MyClass2 = dojo.declare(MyBase, { + // | // constructor, properties, and methods go here + // | // ... + // | }); + // | var MyDiamond = dojo.declare([MyClass1, MyClass2], { + // | // constructor, properties, and methods go here + // | // ... + // | }); + // + // example: + // | var F = function(){ console.log("raw constructor"); }; + // | F.prototype.method = function(){ + // | console.log("raw method"); + // | }; + // | var A = dojo.declare(F, { + // | constructor: function(){ + // | console.log("A.constructor"); + // | }, + // | method: function(){ + // | console.log("before calling F.method..."); + // | this.inherited(arguments); + // | console.log("...back in A"); + // | } + // | }); + // | new A().method(); + // | // will print: + // | // raw constructor + // | // A.constructor + // | // before calling F.method... + // | // raw method + // | // ...back in A + // + // example: + // | var A = dojo.declare(null, { + // | "-chains-": { + // | destroy: "before" + // | } + // | }); + // | var B = dojo.declare(A, { + // | constructor: function(){ + // | console.log("B.constructor"); + // | }, + // | destroy: function(){ + // | console.log("B.destroy"); + // | } + // | }); + // | var C = dojo.declare(B, { + // | constructor: function(){ + // | console.log("C.constructor"); + // | }, + // | destroy: function(){ + // | console.log("C.destroy"); + // | } + // | }); + // | new C().destroy(); + // | // prints: + // | // B.constructor + // | // C.constructor + // | // C.destroy + // | // B.destroy + // + // example: + // | var A = dojo.declare(null, { + // | "-chains-": { + // | constructor: "manual" + // | } + // | }); + // | var B = dojo.declare(A, { + // | constructor: function(){ + // | // ... + // | // call the base constructor with new parameters + // | this.inherited(arguments, [1, 2, 3]); + // | // ... + // | } + // | }); + // + // example: + // | var A = dojo.declare(null, { + // | "-chains-": { + // | m1: "before" + // | }, + // | m1: function(){ + // | console.log("A.m1"); + // | }, + // | m2: function(){ + // | console.log("A.m2"); + // | } + // | }); + // | var B = dojo.declare(A, { + // | "-chains-": { + // | m2: "after" + // | }, + // | m1: function(){ + // | console.log("B.m1"); + // | }, + // | m2: function(){ + // | console.log("B.m2"); + // | } + // | }); + // | var x = new B(); + // | x.m1(); + // | // prints: + // | // B.m1 + // | // A.m1 + // | x.m2(); + // | // prints: + // | // A.m2 + // | // B.m2 + return new Function(); // Function + }; + =====*/ + + /*===== + dojo.safeMixin = function(target, source){ + // summary: + // Mix in properties skipping a constructor and decorating functions + // like it is done by dojo.declare. + // target: Object + // Target object to accept new properties. + // source: Object + // Source object for new properties. + // description: + // This function is used to mix in properties like dojo._mixin does, + // but it skips a constructor property and decorates functions like + // dojo.declare does. + // + // It is meant to be used with classes and objects produced with + // dojo.declare. Functions mixed in with dojo.safeMixin can use + // this.inherited() like normal methods. + // + // This function is used to implement extend() method of a constructor + // produced with dojo.declare(). + // + // example: + // | var A = dojo.declare(null, { + // | m1: function(){ + // | console.log("A.m1"); + // | }, + // | m2: function(){ + // | console.log("A.m2"); + // | } + // | }); + // | var B = dojo.declare(A, { + // | m1: function(){ + // | this.inherited(arguments); + // | console.log("B.m1"); + // | } + // | }); + // | B.extend({ + // | m2: function(){ + // | this.inherited(arguments); + // | console.log("B.m2"); + // | } + // | }); + // | var x = new B(); + // | dojo.safeMixin(x, { + // | m1: function(){ + // | this.inherited(arguments); + // | console.log("X.m1"); + // | }, + // | m2: function(){ + // | this.inherited(arguments); + // | console.log("X.m2"); + // | } + // | }); + // | x.m2(); + // | // prints: + // | // A.m1 + // | // B.m1 + // | // X.m1 + }; + =====*/ + + /*===== + Object.inherited = function(name, args, newArgs){ + // summary: + // Calls a super method. + // name: String? + // The optional method name. Should be the same as the caller's + // name. Usually "name" is specified in complex dynamic cases, when + // the calling method was dynamically added, undecorated by + // dojo.declare, and it cannot be determined. + // args: Arguments + // The caller supply this argument, which should be the original + // "arguments". + // newArgs: Object? + // If "true", the found function will be returned without + // executing it. + // If Array, it will be used to call a super method. Otherwise + // "args" will be used. + // returns: + // Whatever is returned by a super method, or a super method itself, + // if "true" was specified as newArgs. + // description: + // This method is used inside method of classes produced with + // dojo.declare to call a super method (next in the chain). It is + // used for manually controlled chaining. Consider using the regular + // chaining, because it is faster. Use "this.inherited()" only in + // complex cases. + // + // This method cannot me called from automatically chained + // constructors including the case of a special (legacy) + // constructor chaining. It cannot be called from chained methods. + // + // If "this.inherited()" cannot find the next-in-chain method, it + // does nothing and returns "undefined". The last method in chain + // can be a default method implemented in Object, which will be + // called last. + // + // If "name" is specified, it is assumed that the method that + // received "args" is the parent method for this call. It is looked + // up in the chain list and if it is found the next-in-chain method + // is called. If it is not found, the first-in-chain method is + // called. + // + // If "name" is not specified, it will be derived from the calling + // method (using a methoid property "nom"). + // + // example: + // | var B = dojo.declare(A, { + // | method1: function(a, b, c){ + // | this.inherited(arguments); + // | }, + // | method2: function(a, b){ + // | return this.inherited(arguments, [a + b]); + // | } + // | }); + // | // next method is not in the chain list because it is added + // | // manually after the class was created. + // | B.prototype.method3 = function(){ + // | console.log("This is a dynamically-added method."); + // | this.inherited("method3", arguments); + // | }; + // example: + // | var B = dojo.declare(A, { + // | method: function(a, b){ + // | var super = this.inherited(arguments, true); + // | // ... + // | if(!super){ + // | console.log("there is no super method"); + // | return 0; + // | } + // | return super.apply(this, arguments); + // | } + // | }); + return {}; // Object + } + =====*/ + + /*===== + Object.getInherited = function(name, args){ + // summary: + // Returns a super method. + // name: String? + // The optional method name. Should be the same as the caller's + // name. Usually "name" is specified in complex dynamic cases, when + // the calling method was dynamically added, undecorated by + // dojo.declare, and it cannot be determined. + // args: Arguments + // The caller supply this argument, which should be the original + // "arguments". + // returns: + // Returns a super method (Function) or "undefined". + // description: + // This method is a convenience method for "this.inherited()". + // It uses the same algorithm but instead of executing a super + // method, it returns it, or "undefined" if not found. + // + // example: + // | var B = dojo.declare(A, { + // | method: function(a, b){ + // | var super = this.getInherited(arguments); + // | // ... + // | if(!super){ + // | console.log("there is no super method"); + // | return 0; + // | } + // | return super.apply(this, arguments); + // | } + // | }); + return {}; // Object + } + =====*/ + + /*===== + Object.isInstanceOf = function(cls){ + // summary: + // Checks the inheritance chain to see if it is inherited from this + // class. + // cls: Function + // Class constructor. + // returns: + // "true", if this object is inherited from this class, "false" + // otherwise. + // description: + // This method is used with instances of classes produced with + // dojo.declare to determine of they support a certain interface or + // not. It models "instanceof" operator. + // + // example: + // | var A = dojo.declare(null, { + // | // constructor, properties, and methods go here + // | // ... + // | }); + // | var B = dojo.declare(null, { + // | // constructor, properties, and methods go here + // | // ... + // | }); + // | var C = dojo.declare([A, B], { + // | // constructor, properties, and methods go here + // | // ... + // | }); + // | var D = dojo.declare(A, { + // | // constructor, properties, and methods go here + // | // ... + // | }); + // | + // | var a = new A(), b = new B(), c = new C(), d = new D(); + // | + // | console.log(a.isInstanceOf(A)); // true + // | console.log(b.isInstanceOf(A)); // false + // | console.log(c.isInstanceOf(A)); // true + // | console.log(d.isInstanceOf(A)); // true + // | + // | console.log(a.isInstanceOf(B)); // false + // | console.log(b.isInstanceOf(B)); // true + // | console.log(c.isInstanceOf(B)); // true + // | console.log(d.isInstanceOf(B)); // false + // | + // | console.log(a.isInstanceOf(C)); // false + // | console.log(b.isInstanceOf(C)); // false + // | console.log(c.isInstanceOf(C)); // true + // | console.log(d.isInstanceOf(C)); // false + // | + // | console.log(a.isInstanceOf(D)); // false + // | console.log(b.isInstanceOf(D)); // false + // | console.log(c.isInstanceOf(D)); // false + // | console.log(d.isInstanceOf(D)); // true + return {}; // Object + } + =====*/ + + /*===== + Object.extend = function(source){ + // summary: + // Adds all properties and methods of source to constructor's + // prototype, making them available to all instances created with + // constructor. This method is specific to constructors created with + // dojo.declare. + // source: Object + // Source object which properties are going to be copied to the + // constructor's prototype. + // description: + // Adds source properties to the constructor's prototype. It can + // override existing properties. + // + // This method is similar to dojo.extend function, but it is specific + // to constructors produced by dojo.declare. It is implemented + // using dojo.safeMixin, and it skips a constructor property, + // and properly decorates copied functions. + // + // example: + // | var A = dojo.declare(null, { + // | m1: function(){}, + // | s1: "Popokatepetl" + // | }); + // | A.extend({ + // | m1: function(){}, + // | m2: function(){}, + // | f1: true, + // | d1: 42 + // | }); + }; + =====*/ })(); + } diff --git a/lib/dojo/_base/event.js b/lib/dojo/_base/event.js index 1e6ef788..5268c6cf 100644 --- a/lib/dojo/_base/event.js +++ b/lib/dojo/_base/event.js @@ -5,355 +5,641 @@ */ -if(!dojo._hasResource["dojo._base.event"]){ -dojo._hasResource["dojo._base.event"]=true; +if(!dojo._hasResource["dojo._base.event"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.event"] = true; dojo.provide("dojo._base.event"); dojo.require("dojo._base.connect"); + +// this file courtesy of the TurboAjax Group, licensed under a Dojo CLA + (function(){ -var _1=(dojo._event_listener={add:function(_2,_3,fp){ -if(!_2){ -return; -} -_3=_1._normalizeEventName(_3); -fp=_1._fixCallback(_3,fp); -var _4=_3; -if(!dojo.isIE&&(_3=="mouseenter"||_3=="mouseleave")){ -var _5=fp; -_3=(_3=="mouseenter")?"mouseover":"mouseout"; -fp=function(e){ -if(!dojo.isDescendant(e.relatedTarget,_2)){ -return _5.call(this,e); -} -}; -} -_2.addEventListener(_3,fp,false); -return fp; -},remove:function(_6,_7,_8){ -if(_6){ -_7=_1._normalizeEventName(_7); -if(!dojo.isIE&&(_7=="mouseenter"||_7=="mouseleave")){ -_7=(_7=="mouseenter")?"mouseover":"mouseout"; -} -_6.removeEventListener(_7,_8,false); -} -},_normalizeEventName:function(_9){ -return _9.slice(0,2)=="on"?_9.slice(2):_9; -},_fixCallback:function(_a,fp){ -return _a!="keypress"?fp:function(e){ -return fp.call(this,_1._fixEvent(e,this)); -}; -},_fixEvent:function(_b,_c){ -switch(_b.type){ -case "keypress": -_1._setKeyChar(_b); -break; -} -return _b; -},_setKeyChar:function(_d){ -_d.keyChar=_d.charCode?String.fromCharCode(_d.charCode):""; -_d.charOrCode=_d.keyChar||_d.keyCode; -},_punctMap:{106:42,111:47,186:59,187:43,188:44,189:45,190:46,191:47,192:96,219:91,220:92,221:93,222:39}}); -dojo.fixEvent=function(_e,_f){ -return _1._fixEvent(_e,_f); -}; -dojo.stopEvent=function(evt){ -evt.preventDefault(); -evt.stopPropagation(); -}; -var _10=dojo._listener; -dojo._connect=function(obj,_11,_12,_13,_14){ -var _15=obj&&(obj.nodeType||obj.attachEvent||obj.addEventListener); -var lid=_15?(_14?2:1):0,l=[dojo._listener,_1,_10][lid]; -var h=l.add(obj,_11,dojo.hitch(_12,_13)); -return [obj,_11,h,lid]; -}; -dojo._disconnect=function(obj,_16,_17,_18){ -([dojo._listener,_1,_10][_18]).remove(obj,_16,_17); -}; -dojo.keys={BACKSPACE:8,TAB:9,CLEAR:12,ENTER:13,SHIFT:16,CTRL:17,ALT:18,META:dojo.isSafari?91:224,PAUSE:19,CAPS_LOCK:20,ESCAPE:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT_ARROW:37,UP_ARROW:38,RIGHT_ARROW:39,DOWN_ARROW:40,INSERT:45,DELETE:46,HELP:47,LEFT_WINDOW:91,RIGHT_WINDOW:92,SELECT:93,NUMPAD_0:96,NUMPAD_1:97,NUMPAD_2:98,NUMPAD_3:99,NUMPAD_4:100,NUMPAD_5:101,NUMPAD_6:102,NUMPAD_7:103,NUMPAD_8:104,NUMPAD_9:105,NUMPAD_MULTIPLY:106,NUMPAD_PLUS:107,NUMPAD_ENTER:108,NUMPAD_MINUS:109,NUMPAD_PERIOD:110,NUMPAD_DIVIDE:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,NUM_LOCK:144,SCROLL_LOCK:145,copyKey:dojo.isMac&&!dojo.isAIR?(dojo.isSafari?91:224):17}; -var _19=dojo.isMac?"metaKey":"ctrlKey"; -dojo.isCopyKey=function(e){ -return e[_19]; -}; -if(dojo.isIE){ -dojo.mouseButtons={LEFT:1,MIDDLE:4,RIGHT:2,isButton:function(e,_1a){ -return e.button&_1a; -},isLeft:function(e){ -return e.button&1; -},isMiddle:function(e){ -return e.button&4; -},isRight:function(e){ -return e.button&2; -}}; -}else{ -dojo.mouseButtons={LEFT:0,MIDDLE:1,RIGHT:2,isButton:function(e,_1b){ -return e.button==_1b; -},isLeft:function(e){ -return e.button==0; -},isMiddle:function(e){ -return e.button==1; -},isRight:function(e){ -return e.button==2; -}}; -} -if(dojo.isIE){ -var _1c=function(e,_1d){ -try{ -return (e.keyCode=_1d); -} -catch(e){ -return 0; -} -}; -var iel=dojo._listener; -var _1e=(dojo._ieListenersName="_"+dojo._scopeName+"_listeners"); -if(!dojo.config._allow_leaks){ -_10=iel=dojo._ie_listener={handlers:[],add:function(_1f,_20,_21){ -_1f=_1f||dojo.global; -var f=_1f[_20]; -if(!f||!f[_1e]){ -var d=dojo._getIeDispatcher(); -d.target=f&&(ieh.push(f)-1); -d[_1e]=[]; -f=_1f[_20]=d; -} -return f[_1e].push(ieh.push(_21)-1); -},remove:function(_22,_23,_24){ -var f=(_22||dojo.global)[_23],l=f&&f[_1e]; -if(f&&l&&_24--){ -delete ieh[l[_24]]; -delete l[_24]; -} -}}; -var ieh=iel.handlers; -} -dojo.mixin(_1,{add:function(_25,_26,fp){ -if(!_25){ -return; -} -_26=_1._normalizeEventName(_26); -if(_26=="onkeypress"){ -var kd=_25.onkeydown; -if(!kd||!kd[_1e]||!kd._stealthKeydownHandle){ -var h=_1.add(_25,"onkeydown",_1._stealthKeyDown); -kd=_25.onkeydown; -kd._stealthKeydownHandle=h; -kd._stealthKeydownRefs=1; -}else{ -kd._stealthKeydownRefs++; -} -} -return iel.add(_25,_26,_1._fixCallback(fp)); -},remove:function(_27,_28,_29){ -_28=_1._normalizeEventName(_28); -iel.remove(_27,_28,_29); -if(_28=="onkeypress"){ -var kd=_27.onkeydown; -if(--kd._stealthKeydownRefs<=0){ -iel.remove(_27,"onkeydown",kd._stealthKeydownHandle); -delete kd._stealthKeydownHandle; -} -} -},_normalizeEventName:function(_2a){ -return _2a.slice(0,2)!="on"?"on"+_2a:_2a; -},_nop:function(){ -},_fixEvent:function(evt,_2b){ -if(!evt){ -var w=_2b&&(_2b.ownerDocument||_2b.document||_2b).parentWindow||window; -evt=w.event; -} -if(!evt){ -return (evt); -} -evt.target=evt.srcElement; -evt.currentTarget=(_2b||evt.srcElement); -evt.layerX=evt.offsetX; -evt.layerY=evt.offsetY; -var se=evt.srcElement,doc=(se&&se.ownerDocument)||document; -var _2c=((dojo.isIE<6)||(doc["compatMode"]=="BackCompat"))?doc.body:doc.documentElement; -var _2d=dojo._getIeDocumentElementOffset(); -evt.pageX=evt.clientX+dojo._fixIeBiDiScrollLeft(_2c.scrollLeft||0)-_2d.x; -evt.pageY=evt.clientY+(_2c.scrollTop||0)-_2d.y; -if(evt.type=="mouseover"){ -evt.relatedTarget=evt.fromElement; -} -if(evt.type=="mouseout"){ -evt.relatedTarget=evt.toElement; -} -evt.stopPropagation=_1._stopPropagation; -evt.preventDefault=_1._preventDefault; -return _1._fixKeys(evt); -},_fixKeys:function(evt){ -switch(evt.type){ -case "keypress": -var c=("charCode" in evt?evt.charCode:evt.keyCode); -if(c==10){ -c=0; -evt.keyCode=13; -}else{ -if(c==13||c==27){ -c=0; -}else{ -if(c==3){ -c=99; -} -} -} -evt.charCode=c; -_1._setKeyChar(evt); -break; -} -return evt; -},_stealthKeyDown:function(evt){ -var kp=evt.currentTarget.onkeypress; -if(!kp||!kp[_1e]){ -return; -} -var k=evt.keyCode; -var _2e=k!=13&&k!=32&&k!=27&&(k<48||k>90)&&(k<96||k>111)&&(k<186||k>192)&&(k<219||k>222); -if(_2e||evt.ctrlKey){ -var c=_2e?0:k; -if(evt.ctrlKey){ -if(k==3||k==13){ -return; -}else{ -if(c>95&&c<106){ -c-=48; -}else{ -if((!evt.shiftKey)&&(c>=65&&c<=90)){ -c+=32; -}else{ -c=_1._punctMap[c]||c; -} -} -} -} -var _2f=_1._synthesizeEvent(evt,{type:"keypress",faux:true,charCode:c}); -kp.call(evt.currentTarget,_2f); -evt.cancelBubble=_2f.cancelBubble; -evt.returnValue=_2f.returnValue; -_1c(evt,_2f.keyCode); -} -},_stopPropagation:function(){ -this.cancelBubble=true; -},_preventDefault:function(){ -this.bubbledKeyCode=this.keyCode; -if(this.ctrlKey){ -_1c(this,0); -} -this.returnValue=false; -}}); -dojo.stopEvent=function(evt){ -evt=evt||window.event; -_1._stopPropagation.call(evt); -_1._preventDefault.call(evt); -}; -} -_1._synthesizeEvent=function(evt,_30){ -var _31=dojo.mixin({},evt,_30); -_1._setKeyChar(_31); -_31.preventDefault=function(){ -evt.preventDefault(); -}; -_31.stopPropagation=function(){ -evt.stopPropagation(); -}; -return _31; -}; -if(dojo.isOpera){ -dojo.mixin(_1,{_fixEvent:function(evt,_32){ -switch(evt.type){ -case "keypress": -var c=evt.which; -if(c==3){ -c=99; -} -c=c<41&&!evt.shiftKey?0:c; -if(evt.ctrlKey&&!evt.shiftKey&&c>=65&&c<=90){ -c+=32; -} -return _1._synthesizeEvent(evt,{charCode:c}); -} -return evt; -}}); -} -if(dojo.isWebKit){ -_1._add=_1.add; -_1._remove=_1.remove; -dojo.mixin(_1,{add:function(_33,_34,fp){ -if(!_33){ -return; -} -var _35=_1._add(_33,_34,fp); -if(_1._normalizeEventName(_34)=="keypress"){ -_35._stealthKeyDownHandle=_1._add(_33,"keydown",function(evt){ -var k=evt.keyCode; -var _36=k!=13&&k!=32&&(k<48||k>90)&&(k<96||k>111)&&(k<186||k>192)&&(k<219||k>222); -if(_36||evt.ctrlKey){ -var c=_36?0:k; -if(evt.ctrlKey){ -if(k==3||k==13){ -return; -}else{ -if(c>95&&c<106){ -c-=48; -}else{ -if(!evt.shiftKey&&c>=65&&c<=90){ -c+=32; -}else{ -c=_1._punctMap[c]||c; -} -} -} -} -var _37=_1._synthesizeEvent(evt,{type:"keypress",faux:true,charCode:c}); -fp.call(evt.currentTarget,_37); -} -}); -} -return _35; -},remove:function(_38,_39,_3a){ -if(_38){ -if(_3a._stealthKeyDownHandle){ -_1._remove(_38,"keydown",_3a._stealthKeyDownHandle); -} -_1._remove(_38,_39,_3a); -} -},_fixEvent:function(evt,_3b){ -switch(evt.type){ -case "keypress": -if(evt.faux){ -return evt; -} -var c=evt.charCode; -c=c>=32?c:0; -return _1._synthesizeEvent(evt,{charCode:c,faux:true}); -} -return evt; -}}); -} -})(); + // DOM event listener machinery + var del = (dojo._event_listener = { + add: function(/*DOMNode*/ node, /*String*/ name, /*Function*/ fp){ + if(!node){return;} + name = del._normalizeEventName(name); + fp = del._fixCallback(name, fp); + var oname = name; + if( + !dojo.isIE && + (name == "mouseenter" || name == "mouseleave") + ){ + var ofp = fp; + //oname = name; + name = (name == "mouseenter") ? "mouseover" : "mouseout"; + fp = function(e){ + if(!dojo.isDescendant(e.relatedTarget, node)){ + // e.type = oname; // FIXME: doesn't take? SJM: event.type is generally immutable. + return ofp.call(this, e); + } + } + } + node.addEventListener(name, fp, false); + return fp; /*Handle*/ + }, + remove: function(/*DOMNode*/ node, /*String*/ event, /*Handle*/ handle){ + // summary: + // clobbers the listener from the node + // node: + // DOM node to attach the event to + // event: + // the name of the handler to remove the function from + // handle: + // the handle returned from add + if(node){ + event = del._normalizeEventName(event); + if(!dojo.isIE && (event == "mouseenter" || event == "mouseleave")){ + event = (event == "mouseenter") ? "mouseover" : "mouseout"; + } + + node.removeEventListener(event, handle, false); + } + }, + _normalizeEventName: function(/*String*/ name){ + // Generally, name should be lower case, unless it is special + // somehow (e.g. a Mozilla DOM event). + // Remove 'on'. + return name.slice(0,2) =="on" ? name.slice(2) : name; + }, + _fixCallback: function(/*String*/ name, fp){ + // By default, we only invoke _fixEvent for 'keypress' + // If code is added to _fixEvent for other events, we have + // to revisit this optimization. + // This also applies to _fixEvent overrides for Safari and Opera + // below. + return name != "keypress" ? fp : function(e){ return fp.call(this, del._fixEvent(e, this)); }; + }, + _fixEvent: function(evt, sender){ + // _fixCallback only attaches us to keypress. + // Switch on evt.type anyway because we might + // be called directly from dojo.fixEvent. + switch(evt.type){ + case "keypress": + del._setKeyChar(evt); + break; + } + return evt; + }, + _setKeyChar: function(evt){ + evt.keyChar = evt.charCode ? String.fromCharCode(evt.charCode) : ''; + evt.charOrCode = evt.keyChar || evt.keyCode; + }, + // For IE and Safari: some ctrl-key combinations (mostly w/punctuation) do not emit a char code in IE + // we map those virtual key codes to ascii here + // not valid for all (non-US) keyboards, so maybe we shouldn't bother + _punctMap: { + 106:42, + 111:47, + 186:59, + 187:43, + 188:44, + 189:45, + 190:46, + 191:47, + 192:96, + 219:91, + 220:92, + 221:93, + 222:39 + } + }); + + // DOM events + + dojo.fixEvent = function(/*Event*/ evt, /*DOMNode*/ sender){ + // summary: + // normalizes properties on the event object including event + // bubbling methods, keystroke normalization, and x/y positions + // evt: Event + // native event object + // sender: DOMNode + // node to treat as "currentTarget" + return del._fixEvent(evt, sender); + } + + dojo.stopEvent = function(/*Event*/ evt){ + // summary: + // prevents propagation and clobbers the default action of the + // passed event + // evt: Event + // The event object. If omitted, window.event is used on IE. + evt.preventDefault(); + evt.stopPropagation(); + // NOTE: below, this method is overridden for IE + } + + // the default listener to use on dontFix nodes, overriden for IE + var node_listener = dojo._listener; + + // Unify connect and event listeners + dojo._connect = function(obj, event, context, method, dontFix){ + // FIXME: need a more strict test + var isNode = obj && (obj.nodeType||obj.attachEvent||obj.addEventListener); + // choose one of three listener options: raw (connect.js), DOM event on a Node, custom event on a Node + // we need the third option to provide leak prevention on broken browsers (IE) + var lid = isNode ? (dontFix ? 2 : 1) : 0, l = [dojo._listener, del, node_listener][lid]; + // create a listener + var h = l.add(obj, event, dojo.hitch(context, method)); + // formerly, the disconnect package contained "l" directly, but if client code + // leaks the disconnect package (by connecting it to a node), referencing "l" + // compounds the problem. + // instead we return a listener id, which requires custom _disconnect below. + // return disconnect package + return [ obj, event, h, lid ]; + } + + dojo._disconnect = function(obj, event, handle, listener){ + ([dojo._listener, del, node_listener][listener]).remove(obj, event, handle); + } + + // Constants + + // Public: client code should test + // keyCode against these named constants, as the + // actual codes can vary by browser. + dojo.keys = { + // summary: + // Definitions for common key values + BACKSPACE: 8, + TAB: 9, + CLEAR: 12, + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + META: dojo.isSafari ? 91 : 224, // the apple key on macs + PAUSE: 19, + CAPS_LOCK: 20, + ESCAPE: 27, + SPACE: 32, + PAGE_UP: 33, + PAGE_DOWN: 34, + END: 35, + HOME: 36, + LEFT_ARROW: 37, + UP_ARROW: 38, + RIGHT_ARROW: 39, + DOWN_ARROW: 40, + INSERT: 45, + DELETE: 46, + HELP: 47, + LEFT_WINDOW: 91, + RIGHT_WINDOW: 92, + SELECT: 93, + NUMPAD_0: 96, + NUMPAD_1: 97, + NUMPAD_2: 98, + NUMPAD_3: 99, + NUMPAD_4: 100, + NUMPAD_5: 101, + NUMPAD_6: 102, + NUMPAD_7: 103, + NUMPAD_8: 104, + NUMPAD_9: 105, + NUMPAD_MULTIPLY: 106, + NUMPAD_PLUS: 107, + NUMPAD_ENTER: 108, + NUMPAD_MINUS: 109, + NUMPAD_PERIOD: 110, + NUMPAD_DIVIDE: 111, + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123, + F13: 124, + F14: 125, + F15: 126, + NUM_LOCK: 144, + SCROLL_LOCK: 145, + // virtual key mapping + copyKey: dojo.isMac && !dojo.isAIR ? (dojo.isSafari ? 91 : 224 ) : 17 + }; + + var evtCopyKey = dojo.isMac ? "metaKey" : "ctrlKey"; + + dojo.isCopyKey = function(e){ + // summary: + // Checks an event for the copy key (meta on Mac, and ctrl anywhere else) + // e: Event + // Event object to examine + return e[evtCopyKey]; // Boolean + }; + + // Public: decoding mouse buttons from events + +/*===== + dojo.mouseButtons = { + // LEFT: Number + // Numeric value of the left mouse button for the platform. + LEFT: 0, + // MIDDLE: Number + // Numeric value of the middle mouse button for the platform. + MIDDLE: 1, + // RIGHT: Number + // Numeric value of the right mouse button for the platform. + RIGHT: 2, + + isButton: function(e, button){ + // summary: + // Checks an event object for a pressed button + // e: Event + // Event object to examine + // button: Number + // The button value (example: dojo.mouseButton.LEFT) + return e.button == button; // Boolean + }, + isLeft: function(e){ + // summary: + // Checks an event object for the pressed left button + // e: Event + // Event object to examine + return e.button == 0; // Boolean + }, + isMiddle: function(e){ + // summary: + // Checks an event object for the pressed middle button + // e: Event + // Event object to examine + return e.button == 1; // Boolean + }, + isRight: function(e){ + // summary: + // Checks an event object for the pressed right button + // e: Event + // Event object to examine + return e.button == 2; // Boolean + } + }; +=====*/ + + if(dojo.isIE){ + dojo.mouseButtons = { + LEFT: 1, + MIDDLE: 4, + RIGHT: 2, + // helper functions + isButton: function(e, button){ return e.button & button; }, + isLeft: function(e){ return e.button & 1; }, + isMiddle: function(e){ return e.button & 4; }, + isRight: function(e){ return e.button & 2; } + }; + }else{ + dojo.mouseButtons = { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2, + // helper functions + isButton: function(e, button){ return e.button == button; }, + isLeft: function(e){ return e.button == 0; }, + isMiddle: function(e){ return e.button == 1; }, + isRight: function(e){ return e.button == 2; } + }; + } + + // IE event normalization + if(dojo.isIE){ + var _trySetKeyCode = function(e, code){ + try{ + // squelch errors when keyCode is read-only + // (e.g. if keyCode is ctrl or shift) + return (e.keyCode = code); + }catch(e){ + return 0; + } + } + + // by default, use the standard listener + var iel = dojo._listener; + var listenersName = (dojo._ieListenersName = "_" + dojo._scopeName + "_listeners"); + // dispatcher tracking property + if(!dojo.config._allow_leaks){ + // custom listener that handles leak protection for DOM events + node_listener = iel = dojo._ie_listener = { + // support handler indirection: event handler functions are + // referenced here. Event dispatchers hold only indices. + handlers: [], + // add a listener to an object + add: function(/*Object*/ source, /*String*/ method, /*Function*/ listener){ + source = source || dojo.global; + var f = source[method]; + if(!f||!f[listenersName]){ + var d = dojo._getIeDispatcher(); + // original target function is special + d.target = f && (ieh.push(f) - 1); + // dispatcher holds a list of indices into handlers table + d[listenersName] = []; + // redirect source to dispatcher + f = source[method] = d; + } + return f[listenersName].push(ieh.push(listener) - 1) ; /*Handle*/ + }, + // remove a listener from an object + remove: function(/*Object*/ source, /*String*/ method, /*Handle*/ handle){ + var f = (source||dojo.global)[method], l = f && f[listenersName]; + if(f && l && handle--){ + delete ieh[l[handle]]; + delete l[handle]; + } + } + }; + // alias used above + var ieh = iel.handlers; + } + + dojo.mixin(del, { + add: function(/*DOMNode*/ node, /*String*/ event, /*Function*/ fp){ + if(!node){return;} // undefined + event = del._normalizeEventName(event); + if(event=="onkeypress"){ + // we need to listen to onkeydown to synthesize + // keypress events that otherwise won't fire + // on IE + var kd = node.onkeydown; + if(!kd || !kd[listenersName] || !kd._stealthKeydownHandle){ + var h = del.add(node, "onkeydown", del._stealthKeyDown); + kd = node.onkeydown; + kd._stealthKeydownHandle = h; + kd._stealthKeydownRefs = 1; + }else{ + kd._stealthKeydownRefs++; + } + } + return iel.add(node, event, del._fixCallback(fp)); + }, + remove: function(/*DOMNode*/ node, /*String*/ event, /*Handle*/ handle){ + event = del._normalizeEventName(event); + iel.remove(node, event, handle); + if(event=="onkeypress"){ + var kd = node.onkeydown; + if(--kd._stealthKeydownRefs <= 0){ + iel.remove(node, "onkeydown", kd._stealthKeydownHandle); + delete kd._stealthKeydownHandle; + } + } + }, + _normalizeEventName: function(/*String*/ eventName){ + // Generally, eventName should be lower case, unless it is + // special somehow (e.g. a Mozilla event) + // ensure 'on' + return eventName.slice(0,2) != "on" ? "on" + eventName : eventName; + }, + _nop: function(){}, + _fixEvent: function(/*Event*/ evt, /*DOMNode*/ sender){ + // summary: + // normalizes properties on the event object including event + // bubbling methods, keystroke normalization, and x/y positions + // evt: + // native event object + // sender: + // node to treat as "currentTarget" + if(!evt){ + var w = sender && (sender.ownerDocument || sender.document || sender).parentWindow || window; + evt = w.event; + } + if(!evt){return(evt);} + evt.target = evt.srcElement; + evt.currentTarget = (sender || evt.srcElement); + evt.layerX = evt.offsetX; + evt.layerY = evt.offsetY; + // FIXME: scroll position query is duped from dojo.html to + // avoid dependency on that entire module. Now that HTML is in + // Base, we should convert back to something similar there. + var se = evt.srcElement, doc = (se && se.ownerDocument) || document; + // DO NOT replace the following to use dojo.body(), in IE, document.documentElement should be used + // here rather than document.body + var docBody = ((dojo.isIE < 6) || (doc["compatMode"] == "BackCompat")) ? doc.body : doc.documentElement; + var offset = dojo._getIeDocumentElementOffset(); + evt.pageX = evt.clientX + dojo._fixIeBiDiScrollLeft(docBody.scrollLeft || 0) - offset.x; + evt.pageY = evt.clientY + (docBody.scrollTop || 0) - offset.y; + if(evt.type == "mouseover"){ + evt.relatedTarget = evt.fromElement; + } + if(evt.type == "mouseout"){ + evt.relatedTarget = evt.toElement; + } + evt.stopPropagation = del._stopPropagation; + evt.preventDefault = del._preventDefault; + return del._fixKeys(evt); + }, + _fixKeys: function(evt){ + switch(evt.type){ + case "keypress": + var c = ("charCode" in evt ? evt.charCode : evt.keyCode); + if (c==10){ + // CTRL-ENTER is CTRL-ASCII(10) on IE, but CTRL-ENTER on Mozilla + c=0; + evt.keyCode = 13; + }else if(c==13||c==27){ + c=0; // Mozilla considers ENTER and ESC non-printable + }else if(c==3){ + c=99; // Mozilla maps CTRL-BREAK to CTRL-c + } + // Mozilla sets keyCode to 0 when there is a charCode + // but that stops the event on IE. + evt.charCode = c; + del._setKeyChar(evt); + break; + } + return evt; + }, + _stealthKeyDown: function(evt){ + // IE doesn't fire keypress for most non-printable characters. + // other browsers do, we simulate it here. + var kp = evt.currentTarget.onkeypress; + // only works if kp exists and is a dispatcher + if(!kp || !kp[listenersName]){ return; } + // munge key/charCode + var k=evt.keyCode; + // These are Windows Virtual Key Codes + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/WinUI/WindowsUserInterface/UserInput/VirtualKeyCodes.asp + var unprintable = k!=13 && k!=32 && k!=27 && (k<48||k>90) && (k<96||k>111) && (k<186||k>192) && (k<219||k>222); + // synthesize keypress for most unprintables and CTRL-keys + if(unprintable||evt.ctrlKey){ + var c = unprintable ? 0 : k; + if(evt.ctrlKey){ + if(k==3 || k==13){ + return; // IE will post CTRL-BREAK, CTRL-ENTER as keypress natively + }else if(c>95 && c<106){ + c -= 48; // map CTRL-[numpad 0-9] to ASCII + }else if((!evt.shiftKey)&&(c>=65&&c<=90)){ + c += 32; // map CTRL-[A-Z] to lowercase + }else{ + c = del._punctMap[c] || c; // map other problematic CTRL combinations to ASCII + } + } + // simulate a keypress event + var faux = del._synthesizeEvent(evt, {type: 'keypress', faux: true, charCode: c}); + kp.call(evt.currentTarget, faux); + evt.cancelBubble = faux.cancelBubble; + evt.returnValue = faux.returnValue; + _trySetKeyCode(evt, faux.keyCode); + } + }, + // Called in Event scope + _stopPropagation: function(){ + this.cancelBubble = true; + }, + _preventDefault: function(){ + // Setting keyCode to 0 is the only way to prevent certain keypresses (namely + // ctrl-combinations that correspond to menu accelerator keys). + // Otoh, it prevents upstream listeners from getting this information + // Try to split the difference here by clobbering keyCode only for ctrl + // combinations. If you still need to access the key upstream, bubbledKeyCode is + // provided as a workaround. + this.bubbledKeyCode = this.keyCode; + if(this.ctrlKey){_trySetKeyCode(this, 0);} + this.returnValue = false; + } + }); + + // override stopEvent for IE + dojo.stopEvent = function(evt){ + evt = evt || window.event; + del._stopPropagation.call(evt); + del._preventDefault.call(evt); + } + } + + del._synthesizeEvent = function(evt, props){ + var faux = dojo.mixin({}, evt, props); + del._setKeyChar(faux); + // FIXME: would prefer to use dojo.hitch: dojo.hitch(evt, evt.preventDefault); + // but it throws an error when preventDefault is invoked on Safari + // does Event.preventDefault not support "apply" on Safari? + faux.preventDefault = function(){ evt.preventDefault(); }; + faux.stopPropagation = function(){ evt.stopPropagation(); }; + return faux; + } + + // Opera event normalization + if(dojo.isOpera){ + dojo.mixin(del, { + _fixEvent: function(evt, sender){ + switch(evt.type){ + case "keypress": + var c = evt.which; + if(c==3){ + c=99; // Mozilla maps CTRL-BREAK to CTRL-c + } + // can't trap some keys at all, like INSERT and DELETE + // there is no differentiating info between DELETE and ".", or INSERT and "-" + c = c<41 && !evt.shiftKey ? 0 : c; + if(evt.ctrlKey && !evt.shiftKey && c>=65 && c<=90){ + // lowercase CTRL-[A-Z] keys + c += 32; + } + return del._synthesizeEvent(evt, { charCode: c }); + } + return evt; + } + }); + } + + // Webkit event normalization + if(dojo.isWebKit){ + del._add = del.add; + del._remove = del.remove; + + dojo.mixin(del, { + add: function(/*DOMNode*/ node, /*String*/ event, /*Function*/ fp){ + if(!node){return;} // undefined + var handle = del._add(node, event, fp); + if(del._normalizeEventName(event) == "keypress"){ + // we need to listen to onkeydown to synthesize + // keypress events that otherwise won't fire + // in Safari 3.1+: https://lists.webkit.org/pipermail/webkit-dev/2007-December/002992.html + handle._stealthKeyDownHandle = del._add(node, "keydown", function(evt){ + //A variation on the IE _stealthKeydown function + //Synthesize an onkeypress event, but only for unprintable characters. + var k=evt.keyCode; + // These are Windows Virtual Key Codes + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/WinUI/WindowsUserInterface/UserInput/VirtualKeyCodes.asp + var unprintable = k!=13 && k!=32 && (k<48 || k>90) && (k<96 || k>111) && (k<186 || k>192) && (k<219 || k>222); + // synthesize keypress for most unprintables and CTRL-keys + if(unprintable || evt.ctrlKey){ + var c = unprintable ? 0 : k; + if(evt.ctrlKey){ + if(k==3 || k==13){ + return; // IE will post CTRL-BREAK, CTRL-ENTER as keypress natively + }else if(c>95 && c<106){ + c -= 48; // map CTRL-[numpad 0-9] to ASCII + }else if(!evt.shiftKey && c>=65 && c<=90){ + c += 32; // map CTRL-[A-Z] to lowercase + }else{ + c = del._punctMap[c] || c; // map other problematic CTRL combinations to ASCII + } + } + // simulate a keypress event + var faux = del._synthesizeEvent(evt, {type: 'keypress', faux: true, charCode: c}); + fp.call(evt.currentTarget, faux); + } + }); + } + return handle; /*Handle*/ + }, + + remove: function(/*DOMNode*/ node, /*String*/ event, /*Handle*/ handle){ + if(node){ + if(handle._stealthKeyDownHandle){ + del._remove(node, "keydown", handle._stealthKeyDownHandle); + } + del._remove(node, event, handle); + } + }, + _fixEvent: function(evt, sender){ + switch(evt.type){ + case "keypress": + if(evt.faux){ return evt; } + var c = evt.charCode; + c = c>=32 ? c : 0; + return del._synthesizeEvent(evt, {charCode: c, faux: true}); + } + return evt; + } + }); + } + })(); + if(dojo.isIE){ -dojo._ieDispatcher=function(_3c,_3d){ -var ap=Array.prototype,h=dojo._ie_listener.handlers,c=_3c.callee,ls=c[dojo._ieListenersName],t=h[c.target]; -var r=t&&t.apply(_3d,_3c); -var lls=[].concat(ls); -for(var i in lls){ -var f=h[lls[i]]; -if(!(i in ap)&&f){ -f.apply(_3d,_3c); -} -} -return r; -}; -dojo._getIeDispatcher=function(){ -return new Function(dojo._scopeName+"._ieDispatcher(arguments, this)"); -}; -dojo._event_listener._fixCallback=function(fp){ -var f=dojo._event_listener._fixEvent; -return function(e){ -return fp.call(this,f(e,this)); -}; -}; + // keep this out of the closure + // closing over 'iel' or 'ieh' b0rks leak prevention + // ls[i] is an index into the master handler array + dojo._ieDispatcher = function(args, sender){ + var ap = Array.prototype, + h = dojo._ie_listener.handlers, + c = args.callee, + ls = c[dojo._ieListenersName], + t = h[c.target]; + // return value comes from original target function + var r = t && t.apply(sender, args); + // make local copy of listener array so it's immutable during processing + var lls = [].concat(ls); + // invoke listeners after target function + for(var i in lls){ + var f = h[lls[i]]; + if(!(i in ap) && f){ + f.apply(sender, args); + } + } + return r; + } + dojo._getIeDispatcher = function(){ + // ensure the returned function closes over nothing ("new Function" apparently doesn't close) + return new Function(dojo._scopeName + "._ieDispatcher(arguments, this)"); // function + } + // keep this out of the closure to reduce RAM allocation + dojo._event_listener._fixCallback = function(fp){ + var f = dojo._event_listener._fixEvent; + return function(e){ return fp.call(this, f(e, this)); }; + } } + } diff --git a/lib/dojo/_base/fx.js b/lib/dojo/_base/fx.js index 1c589402..21243c1c 100644 --- a/lib/dojo/_base/fx.js +++ b/lib/dojo/_base/fx.js @@ -5,298 +5,665 @@ */ -if(!dojo._hasResource["dojo._base.fx"]){ -dojo._hasResource["dojo._base.fx"]=true; +if(!dojo._hasResource["dojo._base.fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.fx"] = true; dojo.provide("dojo._base.fx"); dojo.require("dojo._base.Color"); dojo.require("dojo._base.connect"); dojo.require("dojo._base.lang"); dojo.require("dojo._base.html"); + +/* + Animation loosely package based on Dan Pupius' work, contributed under CLA: + http://pupius.co.uk/js/Toolkit.Drawing.js +*/ (function(){ -var d=dojo; -var _1=d._mixin; -dojo._Line=function(_2,_3){ -this.start=_2; -this.end=_3; -}; -dojo._Line.prototype.getValue=function(n){ -return ((this.end-this.start)*n)+this.start; -}; -dojo.Animation=function(_4){ -_1(this,_4); -if(d.isArray(this.curve)){ -this.curve=new d._Line(this.curve[0],this.curve[1]); -} -}; -d._Animation=d.Animation; -d.extend(dojo.Animation,{duration:350,repeat:0,rate:20,_percent:0,_startRepeatCount:0,_getStep:function(){ -var _5=this._percent,_6=this.easing; -return _6?_6(_5):_5; -},_fire:function(_7,_8){ -var a=_8||[]; -if(this[_7]){ -if(d.config.debugAtAllCosts){ -this[_7].apply(this,a); -}else{ -try{ -this[_7].apply(this,a); -} -catch(e){ -console.error("exception in animation handler for:",_7); -console.error(e); -} -} -} -return this; -},play:function(_9,_a){ -var _b=this; -if(_b._delayTimer){ -_b._clearTimer(); -} -if(_a){ -_b._stopTimer(); -_b._active=_b._paused=false; -_b._percent=0; -}else{ -if(_b._active&&!_b._paused){ -return _b; -} -} -_b._fire("beforeBegin",[_b.node]); -var de=_9||_b.delay,_c=dojo.hitch(_b,"_play",_a); -if(de>0){ -_b._delayTimer=setTimeout(_c,de); -return _b; -} -_c(); -return _b; -},_play:function(_d){ -var _e=this; -if(_e._delayTimer){ -_e._clearTimer(); -} -_e._startTime=new Date().valueOf(); -if(_e._paused){ -_e._startTime-=_e.duration*_e._percent; -} -_e._active=true; -_e._paused=false; -var _f=_e.curve.getValue(_e._getStep()); -if(!_e._percent){ -if(!_e._startRepeatCount){ -_e._startRepeatCount=_e.repeat; -} -_e._fire("onBegin",[_f]); -} -_e._fire("onPlay",[_f]); -_e._cycle(); -return _e; -},pause:function(){ -var _10=this; -if(_10._delayTimer){ -_10._clearTimer(); -} -_10._stopTimer(); -if(!_10._active){ -return _10; -} -_10._paused=true; -_10._fire("onPause",[_10.curve.getValue(_10._getStep())]); -return _10; -},gotoPercent:function(_11,_12){ -var _13=this; -_13._stopTimer(); -_13._active=_13._paused=true; -_13._percent=_11; -if(_12){ -_13.play(); -} -return _13; -},stop:function(_14){ -var _15=this; -if(_15._delayTimer){ -_15._clearTimer(); -} -if(!_15._timer){ -return _15; -} -_15._stopTimer(); -if(_14){ -_15._percent=1; -} -_15._fire("onStop",[_15.curve.getValue(_15._getStep())]); -_15._active=_15._paused=false; -return _15; -},status:function(){ -if(this._active){ -return this._paused?"paused":"playing"; -} -return "stopped"; -},_cycle:function(){ -var _16=this; -if(_16._active){ -var _17=new Date().valueOf(); -var _18=(_17-_16._startTime)/(_16.duration); -if(_18>=1){ -_18=1; -} -_16._percent=_18; -if(_16.easing){ -_18=_16.easing(_18); -} -_16._fire("onAnimate",[_16.curve.getValue(_18)]); -if(_16._percent<1){ -_16._startTimer(); -}else{ -_16._active=false; -if(_16.repeat>0){ -_16.repeat--; -_16.play(null,true); -}else{ -if(_16.repeat==-1){ -_16.play(null,true); -}else{ -if(_16._startRepeatCount){ -_16.repeat=_16._startRepeatCount; -_16._startRepeatCount=0; -} -} -} -_16._percent=0; -_16._fire("onEnd",[_16.node]); -!_16.repeat&&_16._stopTimer(); -} -} -return _16; -},_clearTimer:function(){ -clearTimeout(this._delayTimer); -delete this._delayTimer; -}}); -var ctr=0,_19=null,_1a={run:function(){ -}}; -d.extend(d.Animation,{_startTimer:function(){ -if(!this._timer){ -this._timer=d.connect(_1a,"run",this,"_cycle"); -ctr++; -} -if(!_19){ -_19=setInterval(d.hitch(_1a,"run"),this.rate); -} -},_stopTimer:function(){ -if(this._timer){ -d.disconnect(this._timer); -this._timer=null; -ctr--; -} -if(ctr<=0){ -clearInterval(_19); -_19=null; -ctr=0; -} -}}); -var _1b=d.isIE?function(_1c){ -var ns=_1c.style; -if(!ns.width.length&&d.style(_1c,"width")=="auto"){ -ns.width="auto"; -} -}:function(){ -}; -dojo._fade=function(_1d){ -_1d.node=d.byId(_1d.node); -var _1e=_1({properties:{}},_1d),_1f=(_1e.properties.opacity={}); -_1f.start=!("start" in _1e)?function(){ -return +d.style(_1e.node,"opacity")||0; -}:_1e.start; -_1f.end=_1e.end; -var _20=d.animateProperty(_1e); -d.connect(_20,"beforeBegin",d.partial(_1b,_1e.node)); -return _20; -}; -dojo.fadeIn=function(_21){ -return d._fade(_1({end:1},_21)); -}; -dojo.fadeOut=function(_22){ -return d._fade(_1({end:0},_22)); -}; -dojo._defaultEasing=function(n){ -return 0.5+((Math.sin((n+1.5)*Math.PI))/2); -}; -var _23=function(_24){ -this._properties=_24; -for(var p in _24){ -var _25=_24[p]; -if(_25.start instanceof d.Color){ -_25.tempColor=new d.Color(); -} -} -}; -_23.prototype.getValue=function(r){ -var ret={}; -for(var p in this._properties){ -var _26=this._properties[p],_27=_26.start; -if(_27 instanceof d.Color){ -ret[p]=d.blendColors(_27,_26.end,r,_26.tempColor).toCss(); -}else{ -if(!d.isArray(_27)){ -ret[p]=((_26.end-_27)*r)+_27+(p!="opacity"?_26.units||"px":0); -} -} -} -return ret; -}; -dojo.animateProperty=function(_28){ -var n=_28.node=d.byId(_28.node); -if(!_28.easing){ -_28.easing=d._defaultEasing; -} -var _29=new d.Animation(_28); -d.connect(_29,"beforeBegin",_29,function(){ -var pm={}; -for(var p in this.properties){ -if(p=="width"||p=="height"){ -this.node.display="block"; -} -var _2a=this.properties[p]; -if(d.isFunction(_2a)){ -_2a=_2a(n); -} -_2a=pm[p]=_1({},(d.isObject(_2a)?_2a:{end:_2a})); -if(d.isFunction(_2a.start)){ -_2a.start=_2a.start(n); -} -if(d.isFunction(_2a.end)){ -_2a.end=_2a.end(n); -} -var _2b=(p.toLowerCase().indexOf("color")>=0); -function _2c(_2d,p){ -var v={height:_2d.offsetHeight,width:_2d.offsetWidth}[p]; -if(v!==undefined){ -return v; -} -v=d.style(_2d,p); -return (p=="opacity")?+v:(_2b?v:parseFloat(v)); -}; -if(!("end" in _2a)){ -_2a.end=_2c(n,p); -}else{ -if(!("start" in _2a)){ -_2a.start=_2c(n,p); -} -} -if(_2b){ -_2a.start=new d.Color(_2a.start); -_2a.end=new d.Color(_2a.end); -}else{ -_2a.start=(p=="opacity")?+_2a.start:parseFloat(_2a.start); -} -} -this.curve=new _23(pm); -}); -d.connect(_29,"onAnimate",d.hitch(d,"style",_29.node)); -return _29; -}; -dojo.anim=function(_2e,_2f,_30,_31,_32,_33){ -return d.animateProperty({node:_2e,duration:_30||d.Animation.prototype.duration,properties:_2f,easing:_31,onEnd:_32}).play(_33||0); -}; + var d = dojo; + var _mixin = d._mixin; + + dojo._Line = function(/*int*/ start, /*int*/ end){ + // summary: + // dojo._Line is the object used to generate values from a start value + // to an end value + // start: int + // Beginning value for range + // end: int + // Ending value for range + this.start = start; + this.end = end; + }; + + dojo._Line.prototype.getValue = function(/*float*/ n){ + // summary: Returns the point on the line + // n: a floating point number greater than 0 and less than 1 + return ((this.end - this.start) * n) + this.start; // Decimal + }; + + dojo.Animation = function(args){ + // summary: + // A generic animation class that fires callbacks into its handlers + // object at various states. + // description: + // A generic animation class that fires callbacks into its handlers + // object at various states. Nearly all dojo animation functions + // return an instance of this method, usually without calling the + // .play() method beforehand. Therefore, you will likely need to + // call .play() on instances of `dojo.Animation` when one is + // returned. + // args: Object + // The 'magic argument', mixing all the properties into this + // animation instance. + + _mixin(this, args); + if(d.isArray(this.curve)){ + this.curve = new d._Line(this.curve[0], this.curve[1]); + } + + }; + + // Alias to drop come 2.0: + d._Animation = d.Animation; + + d.extend(dojo.Animation, { + // duration: Integer + // The time in milliseonds the animation will take to run + duration: 350, + + /*===== + // curve: dojo._Line|Array + // A two element array of start and end values, or a `dojo._Line` instance to be + // used in the Animation. + curve: null, + + // easing: Function? + // A Function to adjust the acceleration (or deceleration) of the progress + // across a dojo._Line + easing: null, + =====*/ + + // repeat: Integer? + // The number of times to loop the animation + repeat: 0, + + // rate: Integer? + // the time in milliseconds to wait before advancing to next frame + // (used as a fps timer: 1000/rate = fps) + rate: 20 /* 50 fps */, + + /*===== + // delay: Integer? + // The time in milliseconds to wait before starting animation after it + // has been .play()'ed + delay: null, + + // beforeBegin: Event? + // Synthetic event fired before a dojo.Animation begins playing (synchronous) + beforeBegin: null, + + // onBegin: Event? + // Synthetic event fired as a dojo.Animation begins playing (useful?) + onBegin: null, + + // onAnimate: Event? + // Synthetic event fired at each interval of a `dojo.Animation` + onAnimate: null, + + // onEnd: Event? + // Synthetic event fired after the final frame of a `dojo.Animation` + onEnd: null, + + // onPlay: Event? + // Synthetic event fired any time a `dojo.Animation` is play()'ed + onPlay: null, + + // onPause: Event? + // Synthetic event fired when a `dojo.Animation` is paused + onPause: null, + + // onStop: Event + // Synthetic event fires when a `dojo.Animation` is stopped + onStop: null, + + =====*/ + + _percent: 0, + _startRepeatCount: 0, + + _getStep: function(){ + var _p = this._percent, + _e = this.easing + ; + return _e ? _e(_p) : _p; + }, + _fire: function(/*Event*/ evt, /*Array?*/ args){ + // summary: + // Convenience function. Fire event "evt" and pass it the + // arguments specified in "args". + // description: + // Convenience function. Fire event "evt" and pass it the + // arguments specified in "args". + // Fires the callback in the scope of the `dojo.Animation` + // instance. + // evt: + // The event to fire. + // args: + // The arguments to pass to the event. + var a = args||[]; + if(this[evt]){ + if(d.config.debugAtAllCosts){ + this[evt].apply(this, a); + }else{ + try{ + this[evt].apply(this, a); + }catch(e){ + // squelch and log because we shouldn't allow exceptions in + // synthetic event handlers to cause the internal timer to run + // amuck, potentially pegging the CPU. I'm not a fan of this + // squelch, but hopefully logging will make it clear what's + // going on + console.error("exception in animation handler for:", evt); + console.error(e); + } + } + } + return this; // dojo.Animation + }, + + play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ + // summary: + // Start the animation. + // delay: + // How many milliseconds to delay before starting. + // gotoStart: + // If true, starts the animation from the beginning; otherwise, + // starts it from its current position. + // returns: dojo.Animation + // The instance to allow chaining. + + var _t = this; + if(_t._delayTimer){ _t._clearTimer(); } + if(gotoStart){ + _t._stopTimer(); + _t._active = _t._paused = false; + _t._percent = 0; + }else if(_t._active && !_t._paused){ + return _t; + } + + _t._fire("beforeBegin", [_t.node]); + + var de = delay || _t.delay, + _p = dojo.hitch(_t, "_play", gotoStart); + + if(de > 0){ + _t._delayTimer = setTimeout(_p, de); + return _t; + } + _p(); + return _t; + }, + + _play: function(gotoStart){ + var _t = this; + if(_t._delayTimer){ _t._clearTimer(); } + _t._startTime = new Date().valueOf(); + if(_t._paused){ + _t._startTime -= _t.duration * _t._percent; + } + + _t._active = true; + _t._paused = false; + var value = _t.curve.getValue(_t._getStep()); + if(!_t._percent){ + if(!_t._startRepeatCount){ + _t._startRepeatCount = _t.repeat; + } + _t._fire("onBegin", [value]); + } + + _t._fire("onPlay", [value]); + + _t._cycle(); + return _t; // dojo.Animation + }, + + pause: function(){ + // summary: Pauses a running animation. + var _t = this; + if(_t._delayTimer){ _t._clearTimer(); } + _t._stopTimer(); + if(!_t._active){ return _t; /*dojo.Animation*/ } + _t._paused = true; + _t._fire("onPause", [_t.curve.getValue(_t._getStep())]); + return _t; // dojo.Animation + }, + + gotoPercent: function(/*Decimal*/ percent, /*Boolean?*/ andPlay){ + // summary: + // Sets the progress of the animation. + // percent: + // A percentage in decimal notation (between and including 0.0 and 1.0). + // andPlay: + // If true, play the animation after setting the progress. + var _t = this; + _t._stopTimer(); + _t._active = _t._paused = true; + _t._percent = percent; + if(andPlay){ _t.play(); } + return _t; // dojo.Animation + }, + + stop: function(/*boolean?*/ gotoEnd){ + // summary: Stops a running animation. + // gotoEnd: If true, the animation will end. + var _t = this; + if(_t._delayTimer){ _t._clearTimer(); } + if(!_t._timer){ return _t; /* dojo.Animation */ } + _t._stopTimer(); + if(gotoEnd){ + _t._percent = 1; + } + _t._fire("onStop", [_t.curve.getValue(_t._getStep())]); + _t._active = _t._paused = false; + return _t; // dojo.Animation + }, + + status: function(){ + // summary: + // Returns a string token representation of the status of + // the animation, one of: "paused", "playing", "stopped" + if(this._active){ + return this._paused ? "paused" : "playing"; // String + } + return "stopped"; // String + }, + + _cycle: function(){ + var _t = this; + if(_t._active){ + var curr = new Date().valueOf(); + var step = (curr - _t._startTime) / (_t.duration); + + if(step >= 1){ + step = 1; + } + _t._percent = step; + + // Perform easing + if(_t.easing){ + step = _t.easing(step); + } + + _t._fire("onAnimate", [_t.curve.getValue(step)]); + + if(_t._percent < 1){ + _t._startTimer(); + }else{ + _t._active = false; + + if(_t.repeat > 0){ + _t.repeat--; + _t.play(null, true); + }else if(_t.repeat == -1){ + _t.play(null, true); + }else{ + if(_t._startRepeatCount){ + _t.repeat = _t._startRepeatCount; + _t._startRepeatCount = 0; + } + } + _t._percent = 0; + _t._fire("onEnd", [_t.node]); + !_t.repeat && _t._stopTimer(); + } + } + return _t; // dojo.Animation + }, + + _clearTimer: function(){ + // summary: Clear the play delay timer + clearTimeout(this._delayTimer); + delete this._delayTimer; + } + + }); + + // the local timer, stubbed into all Animation instances + var ctr = 0, + timer = null, + runner = { + run: function(){} + }; + + d.extend(d.Animation, { + + _startTimer: function(){ + if(!this._timer){ + this._timer = d.connect(runner, "run", this, "_cycle"); + ctr++; + } + if(!timer){ + timer = setInterval(d.hitch(runner, "run"), this.rate); + } + }, + + _stopTimer: function(){ + if(this._timer){ + d.disconnect(this._timer); + this._timer = null; + ctr--; + } + if(ctr <= 0){ + clearInterval(timer); + timer = null; + ctr = 0; + } + } + + }); + + var _makeFadeable = + d.isIE ? function(node){ + // only set the zoom if the "tickle" value would be the same as the + // default + var ns = node.style; + // don't set the width to auto if it didn't already cascade that way. + // We don't want to f anyones designs + if(!ns.width.length && d.style(node, "width") == "auto"){ + ns.width = "auto"; + } + } : + function(){}; + + dojo._fade = function(/*Object*/ args){ + // summary: + // Returns an animation that will fade the node defined by + // args.node from the start to end values passed (args.start + // args.end) (end is mandatory, start is optional) + + args.node = d.byId(args.node); + var fArgs = _mixin({ properties: {} }, args), + props = (fArgs.properties.opacity = {}); + + props.start = !("start" in fArgs) ? + function(){ + return +d.style(fArgs.node, "opacity")||0; + } : fArgs.start; + props.end = fArgs.end; + + var anim = d.animateProperty(fArgs); + d.connect(anim, "beforeBegin", d.partial(_makeFadeable, fArgs.node)); + + return anim; // dojo.Animation + }; + + /*===== + dojo.__FadeArgs = function(node, duration, easing){ + // node: DOMNode|String + // The node referenced in the animation + // duration: Integer? + // Duration of the animation in milliseconds. + // easing: Function? + // An easing function. + this.node = node; + this.duration = duration; + this.easing = easing; + } + =====*/ + + dojo.fadeIn = function(/*dojo.__FadeArgs*/ args){ + // summary: + // Returns an animation that will fade node defined in 'args' from + // its current opacity to fully opaque. + return d._fade(_mixin({ end: 1 }, args)); // dojo.Animation + }; + + dojo.fadeOut = function(/*dojo.__FadeArgs*/ args){ + // summary: + // Returns an animation that will fade node defined in 'args' + // from its current opacity to fully transparent. + return d._fade(_mixin({ end: 0 }, args)); // dojo.Animation + }; + + dojo._defaultEasing = function(/*Decimal?*/ n){ + // summary: The default easing function for dojo.Animation(s) + return 0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2); + }; + + var PropLine = function(properties){ + // PropLine is an internal class which is used to model the values of + // an a group of CSS properties across an animation lifecycle. In + // particular, the "getValue" function handles getting interpolated + // values between start and end for a particular CSS value. + this._properties = properties; + for(var p in properties){ + var prop = properties[p]; + if(prop.start instanceof d.Color){ + // create a reusable temp color object to keep intermediate results + prop.tempColor = new d.Color(); + } + } + }; + + PropLine.prototype.getValue = function(r){ + var ret = {}; + for(var p in this._properties){ + var prop = this._properties[p], + start = prop.start; + if(start instanceof d.Color){ + ret[p] = d.blendColors(start, prop.end, r, prop.tempColor).toCss(); + }else if(!d.isArray(start)){ + ret[p] = ((prop.end - start) * r) + start + (p != "opacity" ? prop.units || "px" : 0); + } + } + return ret; + }; + + /*===== + dojo.declare("dojo.__AnimArgs", [dojo.__FadeArgs], { + // Properties: Object? + // A hash map of style properties to Objects describing the transition, + // such as the properties of dojo._Line with an additional 'units' property + properties: {} + + //TODOC: add event callbacks + }); + =====*/ + + dojo.animateProperty = function(/*dojo.__AnimArgs*/ args){ + // summary: + // Returns an animation that will transition the properties of + // node defined in `args` depending how they are defined in + // `args.properties` + // + // description: + // `dojo.animateProperty` is the foundation of most `dojo.fx` + // animations. It takes an object of "properties" corresponding to + // style properties, and animates them in parallel over a set + // duration. + // + // example: + // A simple animation that changes the width of the specified node. + // | dojo.animateProperty({ + // | node: "nodeId", + // | properties: { width: 400 }, + // | }).play(); + // Dojo figures out the start value for the width and converts the + // integer specified for the width to the more expressive but + // verbose form `{ width: { end: '400', units: 'px' } }` which you + // can also specify directly. Defaults to 'px' if ommitted. + // + // example: + // Animate width, height, and padding over 2 seconds... the + // pedantic way: + // | dojo.animateProperty({ node: node, duration:2000, + // | properties: { + // | width: { start: '200', end: '400', units:"px" }, + // | height: { start:'200', end: '400', units:"px" }, + // | paddingTop: { start:'5', end:'50', units:"px" } + // | } + // | }).play(); + // Note 'paddingTop' is used over 'padding-top'. Multi-name CSS properties + // are written using "mixed case", as the hyphen is illegal as an object key. + // + // example: + // Plug in a different easing function and register a callback for + // when the animation ends. Easing functions accept values between + // zero and one and return a value on that basis. In this case, an + // exponential-in curve. + // | dojo.animateProperty({ + // | node: "nodeId", + // | // dojo figures out the start value + // | properties: { width: { end: 400 } }, + // | easing: function(n){ + // | return (n==0) ? 0 : Math.pow(2, 10 * (n - 1)); + // | }, + // | onEnd: function(node){ + // | // called when the animation finishes. The animation + // | // target is passed to this function + // | } + // | }).play(500); // delay playing half a second + // + // example: + // Like all `dojo.Animation`s, animateProperty returns a handle to the + // Animation instance, which fires the events common to Dojo FX. Use `dojo.connect` + // to access these events outside of the Animation definiton: + // | var anim = dojo.animateProperty({ + // | node:"someId", + // | properties:{ + // | width:400, height:500 + // | } + // | }); + // | dojo.connect(anim,"onEnd", function(){ + // | console.log("animation ended"); + // | }); + // | // play the animation now: + // | anim.play(); + // + // example: + // Each property can be a function whose return value is substituted along. + // Additionally, each measurement (eg: start, end) can be a function. The node + // reference is passed direcly to callbacks. + // | dojo.animateProperty({ + // | node:"mine", + // | properties:{ + // | height:function(node){ + // | // shrink this node by 50% + // | return dojo.position(node).h / 2 + // | }, + // | width:{ + // | start:function(node){ return 100; }, + // | end:function(node){ return 200; } + // | } + // | } + // | }).play(); + // + + var n = args.node = d.byId(args.node); + if(!args.easing){ args.easing = d._defaultEasing; } + + var anim = new d.Animation(args); + d.connect(anim, "beforeBegin", anim, function(){ + var pm = {}; + for(var p in this.properties){ + // Make shallow copy of properties into pm because we overwrite + // some values below. In particular if start/end are functions + // we don't want to overwrite them or the functions won't be + // called if the animation is reused. + if(p == "width" || p == "height"){ + this.node.display = "block"; + } + var prop = this.properties[p]; + if(d.isFunction(prop)){ + prop = prop(n); + } + prop = pm[p] = _mixin({}, (d.isObject(prop) ? prop: { end: prop })); + + if(d.isFunction(prop.start)){ + prop.start = prop.start(n); + } + if(d.isFunction(prop.end)){ + prop.end = prop.end(n); + } + var isColor = (p.toLowerCase().indexOf("color") >= 0); + function getStyle(node, p){ + // dojo.style(node, "height") can return "auto" or "" on IE; this is more reliable: + var v = { height: node.offsetHeight, width: node.offsetWidth }[p]; + if(v !== undefined){ return v; } + v = d.style(node, p); + return (p == "opacity") ? +v : (isColor ? v : parseFloat(v)); + } + if(!("end" in prop)){ + prop.end = getStyle(n, p); + }else if(!("start" in prop)){ + prop.start = getStyle(n, p); + } + + if(isColor){ + prop.start = new d.Color(prop.start); + prop.end = new d.Color(prop.end); + }else{ + prop.start = (p == "opacity") ? +prop.start : parseFloat(prop.start); + } + } + this.curve = new PropLine(pm); + }); + d.connect(anim, "onAnimate", d.hitch(d, "style", anim.node)); + return anim; // dojo.Animation + }; + + dojo.anim = function( /*DOMNode|String*/ node, + /*Object*/ properties, + /*Integer?*/ duration, + /*Function?*/ easing, + /*Function?*/ onEnd, + /*Integer?*/ delay){ + // summary: + // A simpler interface to `dojo.animateProperty()`, also returns + // an instance of `dojo.Animation` but begins the animation + // immediately, unlike nearly every other Dojo animation API. + // description: + // `dojo.anim` is a simpler (but somewhat less powerful) version + // of `dojo.animateProperty`. It uses defaults for many basic properties + // and allows for positional parameters to be used in place of the + // packed "property bag" which is used for other Dojo animation + // methods. + // + // The `dojo.Animation` object returned from `dojo.anim` will be + // already playing when it is returned from this function, so + // calling play() on it again is (usually) a no-op. + // node: + // a DOM node or the id of a node to animate CSS properties on + // duration: + // The number of milliseconds over which the animation + // should run. Defaults to the global animation default duration + // (350ms). + // easing: + // An easing function over which to calculate acceleration + // and deceleration of the animation through its duration. + // A default easing algorithm is provided, but you may + // plug in any you wish. A large selection of easing algorithms + // are available in `dojo.fx.easing`. + // onEnd: + // A function to be called when the animation finishes + // running. + // delay: + // The number of milliseconds to delay beginning the + // animation by. The default is 0. + // example: + // Fade out a node + // | dojo.anim("id", { opacity: 0 }); + // example: + // Fade out a node over a full second + // | dojo.anim("id", { opacity: 0 }, 1000); + return d.animateProperty({ // dojo.Animation + node: node, + duration: duration || d.Animation.prototype.duration, + properties: properties, + easing: easing, + onEnd: onEnd + }).play(delay || 0); + }; })(); + } diff --git a/lib/dojo/_base/html.js b/lib/dojo/_base/html.js index 05084153..be5fd2aa 100644 --- a/lib/dojo/_base/html.js +++ b/lib/dojo/_base/html.js @@ -5,745 +5,1830 @@ */ -if(!dojo._hasResource["dojo._base.html"]){ -dojo._hasResource["dojo._base.html"]=true; +if(!dojo._hasResource["dojo._base.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.html"] = true; dojo.require("dojo._base.lang"); dojo.provide("dojo._base.html"); + +// FIXME: need to add unit tests for all the semi-public methods + try{ -document.execCommand("BackgroundImageCache",false,true); -} -catch(e){ + document.execCommand("BackgroundImageCache", false, true); +}catch(e){ + // sane browsers don't have cache "issues" } -if(dojo.isIE||dojo.isOpera){ -dojo.byId=function(id,_1){ -if(typeof id!="string"){ -return id; -} -var _2=_1||dojo.doc,te=_2.getElementById(id); -if(te&&(te.attributes.id.value==id||te.id==id)){ -return te; + +// ============================= +// DOM Functions +// ============================= + +/*===== +dojo.byId = function(id, doc){ + // summary: + // Returns DOM node with matching `id` attribute or `null` + // if not found. If `id` is a DomNode, this function is a no-op. + // + // id: String|DOMNode + // A string to match an HTML id attribute or a reference to a DOM Node + // + // doc: Document? + // Document to work in. Defaults to the current value of + // dojo.doc. Can be used to retrieve + // node references from other documents. + // + // example: + // Look up a node by ID: + // | var n = dojo.byId("foo"); + // + // example: + // Check if a node exists, and use it. + // | var n = dojo.byId("bar"); + // | if(n){ doStuff() ... } + // + // example: + // Allow string or DomNode references to be passed to a custom function: + // | var foo = function(nodeOrId){ + // | nodeOrId = dojo.byId(nodeOrId); + // | // ... more stuff + // | } +=====*/ + +if(dojo.isIE || dojo.isOpera){ + dojo.byId = function(id, doc){ + if(typeof id != "string"){ + return id; + } + var _d = doc || dojo.doc, te = _d.getElementById(id); + // attributes.id.value is better than just id in case the + // user has a name=id inside a form + if(te && (te.attributes.id.value == id || te.id == id)){ + return te; + }else{ + var eles = _d.all[id]; + if(!eles || eles.nodeName){ + eles = [eles]; + } + // if more than 1, choose first with the correct id + var i=0; + while((te=eles[i++])){ + if((te.attributes && te.attributes.id && te.attributes.id.value == id) + || te.id == id){ + return te; + } + } + } + }; }else{ -var _3=_2.all[id]; -if(!_3||_3.nodeName){ -_3=[_3]; -} -var i=0; -while((te=_3[i++])){ -if((te.attributes&&te.attributes.id&&te.attributes.id.value==id)||te.id==id){ -return te; -} -} + dojo.byId = function(id, doc){ + // inline'd type check + return (typeof id == "string") ? (doc || dojo.doc).getElementById(id) : id; // DomNode + }; } +/*===== }; -}else{ -dojo.byId=function(id,_4){ -return (typeof id=="string")?(_4||dojo.doc).getElementById(id):id; -}; -} +=====*/ + (function(){ -var d=dojo; -var _5=d.byId; -var _6=null,_7; -d.addOnWindowUnload(function(){ -_6=null; -}); -dojo._destroyElement=dojo.destroy=function(_8){ -_8=_5(_8); -try{ -var _9=_8.ownerDocument; -if(!_6||_7!=_9){ -_6=_9.createElement("div"); -_7=_9; -} -_6.appendChild(_8.parentNode?_8.parentNode.removeChild(_8):_8); -_6.innerHTML=""; -} -catch(e){ -} -}; -dojo.isDescendant=function(_a,_b){ -try{ -_a=_5(_a); -_b=_5(_b); -while(_a){ -if(_a==_b){ -return true; -} -_a=_a.parentNode; -} -} -catch(e){ -} -return false; -}; -dojo.setSelectable=function(_c,_d){ -_c=_5(_c); -if(d.isMozilla){ -_c.style.MozUserSelect=_d?"":"none"; -}else{ -if(d.isKhtml||d.isWebKit){ -_c.style.KhtmlUserSelect=_d?"auto":"none"; -}else{ -if(d.isIE){ -var v=(_c.unselectable=_d?"":"on"); -d.query("*",_c).forEach("item.unselectable = '"+v+"'"); -} -} -} -}; -var _e=function(_f,ref){ -var _10=ref.parentNode; -if(_10){ -_10.insertBefore(_f,ref); -} -}; -var _11=function(_12,ref){ -var _13=ref.parentNode; -if(_13){ -if(_13.lastChild==ref){ -_13.appendChild(_12); -}else{ -_13.insertBefore(_12,ref.nextSibling); -} -} -}; -dojo.place=function(_14,_15,_16){ -_15=_5(_15); -if(typeof _14=="string"){ -_14=_14.charAt(0)=="<"?d._toDom(_14,_15.ownerDocument):_5(_14); -} -if(typeof _16=="number"){ -var cn=_15.childNodes; -if(!cn.length||cn.length<=_16){ -_15.appendChild(_14); -}else{ -_e(_14,cn[_16<0?0:_16]); -} -}else{ -switch(_16){ -case "before": -_e(_14,_15); -break; -case "after": -_11(_14,_15); -break; -case "replace": -_15.parentNode.replaceChild(_14,_15); -break; -case "only": -d.empty(_15); -_15.appendChild(_14); -break; -case "first": -if(_15.firstChild){ -_e(_14,_15.firstChild); -break; -} -default: -_15.appendChild(_14); -} -} -return _14; -}; -dojo.boxModel="content-box"; -if(d.isIE){ -d.boxModel=document.compatMode=="BackCompat"?"border-box":"content-box"; -} -var gcs; -if(d.isWebKit){ -gcs=function(_17){ -var s; -if(_17.nodeType==1){ -var dv=_17.ownerDocument.defaultView; -s=dv.getComputedStyle(_17,null); -if(!s&&_17.style){ -_17.style.display=""; -s=dv.getComputedStyle(_17,null); -} -} -return s||{}; -}; -}else{ -if(d.isIE){ -gcs=function(_18){ -return _18.nodeType==1?_18.currentStyle:{}; -}; -}else{ -gcs=function(_19){ -return _19.nodeType==1?_19.ownerDocument.defaultView.getComputedStyle(_19,null):{}; -}; -} -} -dojo.getComputedStyle=gcs; -if(!d.isIE){ -d._toPixelValue=function(_1a,_1b){ -return parseFloat(_1b)||0; -}; -}else{ -d._toPixelValue=function(_1c,_1d){ -if(!_1d){ -return 0; -} -if(_1d=="medium"){ -return 4; -} -if(_1d.slice&&_1d.slice(-2)=="px"){ -return parseFloat(_1d); -} -with(_1c){ -var _1e=style.left; -var _1f=runtimeStyle.left; -runtimeStyle.left=currentStyle.left; -try{ -style.left=_1d; -_1d=style.pixelLeft; -} -catch(e){ -_1d=0; -} -style.left=_1e; -runtimeStyle.left=_1f; -} -return _1d; -}; -} -var px=d._toPixelValue; -var _20="DXImageTransform.Microsoft.Alpha"; -var af=function(n,f){ -try{ -return n.filters.item(_20); -} -catch(e){ -return f?{}:null; -} -}; -dojo._getOpacity=d.isIE?function(_21){ -try{ -return af(_21).Opacity/100; -} -catch(e){ -return 1; -} -}:function(_22){ -return gcs(_22).opacity; -}; -dojo._setOpacity=d.isIE?function(_23,_24){ -var ov=_24*100,_25=_24==1; -_23.style.zoom=_25?"":1; -if(!af(_23)){ -if(_25){ -return _24; -} -_23.style.filter+=" progid:"+_20+"(Opacity="+ov+")"; -}else{ -af(_23,1).Opacity=ov; -} -af(_23,1).Enabled=!_25; -if(_23.nodeName.toLowerCase()=="tr"){ -d.query("> td",_23).forEach(function(i){ -d._setOpacity(i,_24); -}); -} -return _24; -}:function(_26,_27){ -return _26.style.opacity=_27; -}; -var _28={left:true,top:true}; -var _29=/margin|padding|width|height|max|min|offset/; -var _2a=function(_2b,_2c,_2d){ -_2c=_2c.toLowerCase(); -if(d.isIE){ -if(_2d=="auto"){ -if(_2c=="height"){ -return _2b.offsetHeight; -} -if(_2c=="width"){ -return _2b.offsetWidth; -} -} -if(_2c=="fontweight"){ -switch(_2d){ -case 700: -return "bold"; -case 400: -default: -return "normal"; -} -} -} -if(!(_2c in _28)){ -_28[_2c]=_29.test(_2c); -} -return _28[_2c]?px(_2b,_2d):_2d; -}; -var _2e=d.isIE?"styleFloat":"cssFloat",_2f={"cssFloat":_2e,"styleFloat":_2e,"float":_2e}; -dojo.style=function(_30,_31,_32){ -var n=_5(_30),_33=arguments.length,op=(_31=="opacity"); -_31=_2f[_31]||_31; -if(_33==3){ -return op?d._setOpacity(n,_32):n.style[_31]=_32; -} -if(_33==2&&op){ -return d._getOpacity(n); -} -var s=gcs(n); -if(_33==2&&typeof _31!="string"){ -for(var x in _31){ -d.style(_30,x,_31[x]); -} -return s; -} -return (_33==1)?s:_2a(n,_31,s[_31]||n.style[_31]); -}; -dojo._getPadExtents=function(n,_34){ -var s=_34||gcs(n),l=px(n,s.paddingLeft),t=px(n,s.paddingTop); -return {l:l,t:t,w:l+px(n,s.paddingRight),h:t+px(n,s.paddingBottom)}; -}; -dojo._getBorderExtents=function(n,_35){ -var ne="none",s=_35||gcs(n),bl=(s.borderLeftStyle!=ne?px(n,s.borderLeftWidth):0),bt=(s.borderTopStyle!=ne?px(n,s.borderTopWidth):0); -return {l:bl,t:bt,w:bl+(s.borderRightStyle!=ne?px(n,s.borderRightWidth):0),h:bt+(s.borderBottomStyle!=ne?px(n,s.borderBottomWidth):0)}; -}; -dojo._getPadBorderExtents=function(n,_36){ -var s=_36||gcs(n),p=d._getPadExtents(n,s),b=d._getBorderExtents(n,s); -return {l:p.l+b.l,t:p.t+b.t,w:p.w+b.w,h:p.h+b.h}; -}; -dojo._getMarginExtents=function(n,_37){ -var s=_37||gcs(n),l=px(n,s.marginLeft),t=px(n,s.marginTop),r=px(n,s.marginRight),b=px(n,s.marginBottom); -if(d.isWebKit&&(s.position!="absolute")){ -r=l; -} -return {l:l,t:t,w:l+r,h:t+b}; -}; -dojo._getMarginBox=function(_38,_39){ -var s=_39||gcs(_38),me=d._getMarginExtents(_38,s); -var l=_38.offsetLeft-me.l,t=_38.offsetTop-me.t,p=_38.parentNode; -if(d.isMoz){ -var sl=parseFloat(s.left),st=parseFloat(s.top); -if(!isNaN(sl)&&!isNaN(st)){ -l=sl,t=st; -}else{ -if(p&&p.style){ -var pcs=gcs(p); -if(pcs.overflow!="visible"){ -var be=d._getBorderExtents(p,pcs); -l+=be.l,t+=be.t; -} -} -} -}else{ -if(d.isOpera||(d.isIE>7&&!d.isQuirks)){ -if(p){ -be=d._getBorderExtents(p); -l-=be.l; -t-=be.t; -} -} -} -return {l:l,t:t,w:_38.offsetWidth+me.w,h:_38.offsetHeight+me.h}; -}; -dojo._getContentBox=function(_3a,_3b){ -var s=_3b||gcs(_3a),pe=d._getPadExtents(_3a,s),be=d._getBorderExtents(_3a,s),w=_3a.clientWidth,h; -if(!w){ -w=_3a.offsetWidth,h=_3a.offsetHeight; -}else{ -h=_3a.clientHeight,be.w=be.h=0; -} -if(d.isOpera){ -pe.l+=be.l; -pe.t+=be.t; -} -return {l:pe.l,t:pe.t,w:w-pe.w-be.w,h:h-pe.h-be.h}; -}; -dojo._getBorderBox=function(_3c,_3d){ -var s=_3d||gcs(_3c),pe=d._getPadExtents(_3c,s),cb=d._getContentBox(_3c,s); -return {l:cb.l-pe.l,t:cb.t-pe.t,w:cb.w+pe.w,h:cb.h+pe.h}; -}; -dojo._setBox=function(_3e,l,t,w,h,u){ -u=u||"px"; -var s=_3e.style; -if(!isNaN(l)){ -s.left=l+u; -} -if(!isNaN(t)){ -s.top=t+u; -} -if(w>=0){ -s.width=w+u; -} -if(h>=0){ -s.height=h+u; -} -}; -dojo._isButtonTag=function(_3f){ -return _3f.tagName=="BUTTON"||_3f.tagName=="INPUT"&&(_3f.getAttribute("type")||"").toUpperCase()=="BUTTON"; -}; -dojo._usesBorderBox=function(_40){ -var n=_40.tagName; -return d.boxModel=="border-box"||n=="TABLE"||d._isButtonTag(_40); -}; -dojo._setContentSize=function(_41,_42,_43,_44){ -if(d._usesBorderBox(_41)){ -var pb=d._getPadBorderExtents(_41,_44); -if(_42>=0){ -_42+=pb.w; -} -if(_43>=0){ -_43+=pb.h; -} -} -d._setBox(_41,NaN,NaN,_42,_43); -}; -dojo._setMarginBox=function(_45,_46,_47,_48,_49,_4a){ -var s=_4a||gcs(_45),bb=d._usesBorderBox(_45),pb=bb?_4b:d._getPadBorderExtents(_45,s); -if(d.isWebKit){ -if(d._isButtonTag(_45)){ -var ns=_45.style; -if(_48>=0&&!ns.width){ -ns.width="4px"; -} -if(_49>=0&&!ns.height){ -ns.height="4px"; -} -} -} -var mb=d._getMarginExtents(_45,s); -if(_48>=0){ -_48=Math.max(_48-pb.w-mb.w,0); -} -if(_49>=0){ -_49=Math.max(_49-pb.h-mb.h,0); -} -d._setBox(_45,_46,_47,_48,_49); -}; -var _4b={l:0,t:0,w:0,h:0}; -dojo.marginBox=function(_4c,box){ -var n=_5(_4c),s=gcs(n),b=box; -return !b?d._getMarginBox(n,s):d._setMarginBox(n,b.l,b.t,b.w,b.h,s); -}; -dojo.contentBox=function(_4d,box){ -var n=_5(_4d),s=gcs(n),b=box; -return !b?d._getContentBox(n,s):d._setContentSize(n,b.w,b.h,s); -}; -var _4e=function(_4f,_50){ -if(!(_4f=(_4f||0).parentNode)){ -return 0; -} -var val,_51=0,_52=d.body(); -while(_4f&&_4f.style){ -if(gcs(_4f).position=="fixed"){ -return 0; -} -val=_4f[_50]; -if(val){ -_51+=val-0; -if(_4f==_52){ -break; -} -} -_4f=_4f.parentNode; -} -return _51; -}; -dojo._docScroll=function(){ -var n=d.global; -return "pageXOffset" in n?{x:n.pageXOffset,y:n.pageYOffset}:(n=d.doc.documentElement,n.clientHeight?{x:d._fixIeBiDiScrollLeft(n.scrollLeft),y:n.scrollTop}:(n=d.body(),{x:n.scrollLeft||0,y:n.scrollTop||0})); -}; -dojo._isBodyLtr=function(){ -return "_bodyLtr" in d?d._bodyLtr:d._bodyLtr=(d.body().dir||d.doc.documentElement.dir||"ltr").toLowerCase()=="ltr"; -}; -dojo._getIeDocumentElementOffset=function(){ -var de=d.doc.documentElement; -if(d.isIE<8){ -var r=de.getBoundingClientRect(); -var l=r.left,t=r.top; -if(d.isIE<7){ -l+=de.clientLeft; -t+=de.clientTop; -} -return {x:l<0?0:l,y:t<0?0:t}; -}else{ -return {x:0,y:0}; -} -}; -dojo._fixIeBiDiScrollLeft=function(_53){ -var dd=d.doc; -if(d.isIE<8&&!d._isBodyLtr()){ -var de=d.isQuirks?dd.body:dd.documentElement; -return _53+de.clientWidth-de.scrollWidth; -} -return _53; -}; -dojo._abs=dojo.position=function(_54,_55){ -var db=d.body(),dh=db.parentNode,ret; -_54=_5(_54); -if(_54["getBoundingClientRect"]){ -ret=_54.getBoundingClientRect(); -ret={x:ret.left,y:ret.top,w:ret.right-ret.left,h:ret.bottom-ret.top}; -if(d.isIE){ -var _56=d._getIeDocumentElementOffset(); -ret.x-=_56.x+(d.isQuirks?db.clientLeft+db.offsetLeft:0); -ret.y-=_56.y+(d.isQuirks?db.clientTop+db.offsetTop:0); -}else{ -if(d.isFF==3){ -var cs=gcs(dh); -ret.x-=px(dh,cs.marginLeft)+px(dh,cs.borderLeftWidth); -ret.y-=px(dh,cs.marginTop)+px(dh,cs.borderTopWidth); -} -} -}else{ -ret={x:0,y:0,w:_54.offsetWidth,h:_54.offsetHeight}; -if(_54["offsetParent"]){ -ret.x-=_4e(_54,"scrollLeft"); -ret.y-=_4e(_54,"scrollTop"); -var _57=_54; -do{ -var n=_57.offsetLeft,t=_57.offsetTop; -ret.x+=isNaN(n)?0:n; -ret.y+=isNaN(t)?0:t; -cs=gcs(_57); -if(_57!=_54){ -if(d.isMoz){ -ret.x+=2*px(_57,cs.borderLeftWidth); -ret.y+=2*px(_57,cs.borderTopWidth); -}else{ -ret.x+=px(_57,cs.borderLeftWidth); -ret.y+=px(_57,cs.borderTopWidth); -} -} -if(d.isMoz&&cs.position=="static"){ -var _58=_57.parentNode; -while(_58!=_57.offsetParent){ -var pcs=gcs(_58); -if(pcs.position=="static"){ -ret.x+=px(_57,pcs.borderLeftWidth); -ret.y+=px(_57,pcs.borderTopWidth); -} -_58=_58.parentNode; -} -} -_57=_57.offsetParent; -}while((_57!=dh)&&_57); -}else{ -if(_54.x&&_54.y){ -ret.x+=isNaN(_54.x)?0:_54.x; -ret.y+=isNaN(_54.y)?0:_54.y; -} -} -} -if(_55){ -var _59=d._docScroll(); -ret.x+=_59.x; -ret.y+=_59.y; -} -return ret; -}; -dojo.coords=function(_5a,_5b){ -var n=_5(_5a),s=gcs(n),mb=d._getMarginBox(n,s); -var abs=d.position(n,_5b); -mb.x=abs.x; -mb.y=abs.y; -return mb; -}; -var _5c={"class":"className","for":"htmlFor",tabindex:"tabIndex",readonly:"readOnly",colspan:"colSpan",frameborder:"frameBorder",rowspan:"rowSpan",valuetype:"valueType"},_5d={classname:"class",htmlfor:"for",tabindex:"tabIndex",readonly:"readOnly"},_5e={innerHTML:1,className:1,htmlFor:d.isIE,value:1}; -var _5f=function(_60){ -return _5d[_60.toLowerCase()]||_60; -}; -var _61=function(_62,_63){ -var _64=_62.getAttributeNode&&_62.getAttributeNode(_63); -return _64&&_64.specified; -}; -dojo.hasAttr=function(_65,_66){ -var lc=_66.toLowerCase(); -return _5e[_5c[lc]||_66]||_61(_5(_65),_5d[lc]||_66); -}; -var _67={},_68=0,_69=dojo._scopeName+"attrid",_6a={col:1,colgroup:1,table:1,tbody:1,tfoot:1,thead:1,tr:1,title:1}; -dojo.attr=function(_6b,_6c,_6d){ -_6b=_5(_6b); -var _6e=arguments.length,_6f; -if(_6e==2&&typeof _6c!="string"){ -for(var x in _6c){ -d.attr(_6b,x,_6c[x]); -} -return _6b; -} -var lc=_6c.toLowerCase(),_70=_5c[lc]||_6c,_71=_5e[_70],_72=_5d[lc]||_6c; -if(_6e==3){ -do{ -if(_70=="style"&&typeof _6d!="string"){ -d.style(_6b,_6d); -break; -} -if(_70=="innerHTML"){ -if(d.isIE&&_6b.tagName.toLowerCase() in _6a){ -d.empty(_6b); -_6b.appendChild(d._toDom(_6d,_6b.ownerDocument)); -}else{ -_6b[_70]=_6d; -} -break; -} -if(d.isFunction(_6d)){ -var _73=d.attr(_6b,_69); -if(!_73){ -_73=_68++; -d.attr(_6b,_69,_73); -} -if(!_67[_73]){ -_67[_73]={}; -} -var h=_67[_73][_70]; -if(h){ -d.disconnect(h); -}else{ -try{ -delete _6b[_70]; -} -catch(e){ -} -} -_67[_73][_70]=d.connect(_6b,_70,_6d); -break; -} -if(_71||typeof _6d=="boolean"){ -_6b[_70]=_6d; -break; -} -_6b.setAttribute(_72,_6d); -}while(false); -return _6b; -} -_6d=_6b[_70]; -if(_71&&typeof _6d!="undefined"){ -return _6d; -} -if(_70!="href"&&(typeof _6d=="boolean"||d.isFunction(_6d))){ -return _6d; -} -return _61(_6b,_72)?_6b.getAttribute(_72):null; -}; -dojo.removeAttr=function(_74,_75){ -_5(_74).removeAttribute(_5f(_75)); -}; -dojo.getNodeProp=function(_76,_77){ -_76=_5(_76); -var lc=_77.toLowerCase(),_78=_5c[lc]||_77; -if((_78 in _76)&&_78!="href"){ -return _76[_78]; -} -var _79=_5d[lc]||_77; -return _61(_76,_79)?_76.getAttribute(_79):null; -}; -dojo.create=function(tag,_7a,_7b,pos){ -var doc=d.doc; -if(_7b){ -_7b=_5(_7b); -doc=_7b.ownerDocument; -} -if(typeof tag=="string"){ -tag=doc.createElement(tag); -} -if(_7a){ -d.attr(tag,_7a); -} -if(_7b){ -d.place(tag,_7b,pos); -} -return tag; -}; -d.empty=d.isIE?function(_7c){ -_7c=_5(_7c); -for(var c;c=_7c.lastChild;){ -d.destroy(c); -} -}:function(_7d){ -_5(_7d).innerHTML=""; -}; -var _7e={option:["select"],tbody:["table"],thead:["table"],tfoot:["table"],tr:["table","tbody"],td:["table","tbody","tr"],th:["table","thead","tr"],legend:["fieldset"],caption:["table"],colgroup:["table"],col:["table","colgroup"],li:["ul"]},_7f=/<\s*([\w\:]+)/,_80={},_81=0,_82="__"+d._scopeName+"ToDomId"; -for(var _83 in _7e){ -var tw=_7e[_83]; -tw.pre=_83=="option"?"' : "<" + tw.join("><") + ">"; + tw.post = ""; + // the last line is destructive: it reverses the array, + // but we don't care at this point + } + + d._toDom = function(frag, doc){ + // summary: + // converts HTML string into DOM nodes. + + doc = doc || d.doc; + var masterId = doc[masterName]; + if(!masterId){ + doc[masterName] = masterId = ++masterNum + ""; + masterNode[masterId] = doc.createElement("div"); + } + + // make sure the frag is a string. + frag += ""; + + // find the starting tag, and get node wrapper + var match = frag.match(reTag), + tag = match ? match[1].toLowerCase() : "", + master = masterNode[masterId], + wrap, i, fc, df; + if(match && tagWrap[tag]){ + wrap = tagWrap[tag]; + master.innerHTML = wrap.pre + frag + wrap.post; + for(i = wrap.length; i; --i){ + master = master.firstChild; + } + }else{ + master.innerHTML = frag; + } + + // one node shortcut => return the node itself + if(master.childNodes.length == 1){ + return master.removeChild(master.firstChild); // DOMNode + } + + // return multiple nodes as a document fragment + df = doc.createDocumentFragment(); + while(fc = master.firstChild){ // intentional assignment + df.appendChild(fc); + } + return df; // DOMNode + } + + // ============================= + // (CSS) Class Functions + // ============================= + var _className = "className"; + + dojo.hasClass = function(/*DomNode|String*/node, /*String*/classStr){ + // summary: + // Returns whether or not the specified classes are a portion of the + // class list currently applied to the node. + // + // node: + // String ID or DomNode reference to check the class for. + // + // classStr: + // A string class name to look for. + // + // example: + // Do something if a node with id="someNode" has class="aSillyClassName" present + // | if(dojo.hasClass("someNode","aSillyClassName")){ ... } + + return ((" "+ byId(node)[_className] +" ").indexOf(" " + classStr + " ") >= 0); // Boolean + }; + + var spaces = /\s+/, a1 = [""], + str2array = function(s){ + if(typeof s == "string" || s instanceof String){ + if(s.indexOf(" ") < 0){ + a1[0] = s; + return a1; + }else{ + return s.split(spaces); + } + } + // assumed to be an array + return s || ""; + }; + + dojo.addClass = function(/*DomNode|String*/node, /*String|Array*/classStr){ + // summary: + // Adds the specified classes to the end of the class list on the + // passed node. Will not re-apply duplicate classes. + // + // node: + // String ID or DomNode reference to add a class string too + // + // classStr: + // A String class name to add, or several space-separated class names, + // or an array of class names. + // + // example: + // Add a class to some node: + // | dojo.addClass("someNode", "anewClass"); + // + // example: + // Add two classes at once: + // | dojo.addClass("someNode", "firstClass secondClass"); + // + // example: + // Add two classes at once (using array): + // | dojo.addClass("someNode", ["firstClass", "secondClass"]); + // + // example: + // Available in `dojo.NodeList` for multiple additions + // | dojo.query("ul > li").addClass("firstLevel"); + + node = byId(node); + classStr = str2array(classStr); + var cls = node[_className], oldLen; + cls = cls ? " " + cls + " " : " "; + oldLen = cls.length; + for(var i = 0, len = classStr.length, c; i < len; ++i){ + c = classStr[i]; + if(c && cls.indexOf(" " + c + " ") < 0){ + cls += c + " "; + } + } + if(oldLen < cls.length){ + node[_className] = cls.substr(1, cls.length - 2); + } + }; + + dojo.removeClass = function(/*DomNode|String*/node, /*String|Array?*/classStr){ + // summary: + // Removes the specified classes from node. No `dojo.hasClass` + // check is required. + // + // node: + // String ID or DomNode reference to remove the class from. + // + // classStr: + // An optional String class name to remove, or several space-separated + // class names, or an array of class names. If omitted, all class names + // will be deleted. + // + // example: + // Remove a class from some node: + // | dojo.removeClass("someNode", "firstClass"); + // + // example: + // Remove two classes from some node: + // | dojo.removeClass("someNode", "firstClass secondClass"); + // + // example: + // Remove two classes from some node (using array): + // | dojo.removeClass("someNode", ["firstClass", "secondClass"]); + // + // example: + // Remove all classes from some node: + // | dojo.removeClass("someNode"); + // + // example: + // Available in `dojo.NodeList()` for multiple removal + // | dojo.query(".foo").removeClass("foo"); + + node = byId(node); + var cls; + if(classStr !== undefined){ + classStr = str2array(classStr); + cls = " " + node[_className] + " "; + for(var i = 0, len = classStr.length; i < len; ++i){ + cls = cls.replace(" " + classStr[i] + " ", " "); + } + cls = d.trim(cls); + }else{ + cls = ""; + } + if(node[_className] != cls){ node[_className] = cls; } + }; + + dojo.toggleClass = function(/*DomNode|String*/node, /*String|Array*/classStr, /*Boolean?*/condition){ + // summary: + // Adds a class to node if not present, or removes if present. + // Pass a boolean condition if you want to explicitly add or remove. + // condition: + // If passed, true means to add the class, false means to remove. + // + // example: + // | dojo.toggleClass("someNode", "hovered"); + // + // example: + // Forcefully add a class + // | dojo.toggleClass("someNode", "hovered", true); + // + // example: + // Available in `dojo.NodeList()` for multiple toggles + // | dojo.query(".toggleMe").toggleClass("toggleMe"); + + if(condition === undefined){ + condition = !d.hasClass(node, classStr); + } + d[condition ? "addClass" : "removeClass"](node, classStr); + }; + })(); + } diff --git a/lib/dojo/_base/json.js b/lib/dojo/_base/json.js index 7d8c5af6..4d50400c 100644 --- a/lib/dojo/_base/json.js +++ b/lib/dojo/_base/json.js @@ -5,77 +5,150 @@ */ -if(!dojo._hasResource["dojo._base.json"]){ -dojo._hasResource["dojo._base.json"]=true; +if(!dojo._hasResource["dojo._base.json"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.json"] = true; dojo.provide("dojo._base.json"); -dojo.fromJson=function(_1){ -return eval("("+_1+")"); -}; -dojo._escapeString=function(_2){ -return ("\""+_2.replace(/(["\\])/g,"\\$1")+"\"").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r"); -}; -dojo.toJsonIndentStr="\t"; -dojo.toJson=function(it,_3,_4){ -if(it===undefined){ -return "undefined"; -} -var _5=typeof it; -if(_5=="number"||_5=="boolean"){ -return it+""; -} -if(it===null){ -return "null"; -} -if(dojo.isString(it)){ -return dojo._escapeString(it); -} -var _6=arguments.callee; -var _7; -_4=_4||""; -var _8=_3?_4+dojo.toJsonIndentStr:""; -var tf=it.__json__||it.json; -if(dojo.isFunction(tf)){ -_7=tf.call(it); -if(it!==_7){ -return _6(_7,_3,_8); -} -} -if(it.nodeType&&it.cloneNode){ -throw new Error("Can't serialize DOM nodes"); -} -var _9=_3?" ":""; -var _a=_3?"\n":""; -if(dojo.isArray(it)){ -var _b=dojo.map(it,function(_c){ -var _d=_6(_c,_3,_8); -if(typeof _d!="string"){ -_d="undefined"; -} -return _a+_8+_d; -}); -return "["+_b.join(","+_9)+_a+_4+"]"; -} -if(_5=="function"){ -return null; -} -var _e=[],_f; -for(_f in it){ -var _10,val; -if(typeof _f=="number"){ -_10="\""+_f+"\""; -}else{ -if(typeof _f=="string"){ -_10=dojo._escapeString(_f); -}else{ -continue; -} + +dojo.fromJson = function(/*String*/ json){ + // summary: + // Parses a [JSON](http://json.org) string to return a JavaScript object. + // description: + // Throws for invalid JSON strings, but it does not use a strict JSON parser. It + // delegates to eval(). The content passed to this method must therefore come + // from a trusted source. + // json: + // a string literal of a JSON item, for instance: + // `'{ "foo": [ "bar", 1, { "baz": "thud" } ] }'` + + return eval("(" + json + ")"); // Object } -val=_6(it[_f],_3,_8); -if(typeof val!="string"){ -continue; + +dojo._escapeString = function(/*String*/str){ + //summary: + // Adds escape sequences for non-visual characters, double quote and + // backslash and surrounds with double quotes to form a valid string + // literal. + return ('"' + str.replace(/(["\\])/g, '\\$1') + '"'). + replace(/[\f]/g, "\\f").replace(/[\b]/g, "\\b").replace(/[\n]/g, "\\n"). + replace(/[\t]/g, "\\t").replace(/[\r]/g, "\\r"); // string } -_e.push(_a+_8+_10+":"+_9+val); + +dojo.toJsonIndentStr = "\t"; +dojo.toJson = function(/*Object*/ it, /*Boolean?*/ prettyPrint, /*String?*/ _indentStr){ + // summary: + // Returns a [JSON](http://json.org) serialization of an object. + // description: + // Returns a [JSON](http://json.org) serialization of an object. + // Note that this doesn't check for infinite recursion, so don't do that! + // it: + // an object to be serialized. Objects may define their own + // serialization via a special "__json__" or "json" function + // property. If a specialized serializer has been defined, it will + // be used as a fallback. + // prettyPrint: + // if true, we indent objects and arrays to make the output prettier. + // The variable `dojo.toJsonIndentStr` is used as the indent string -- + // to use something other than the default (tab), change that variable + // before calling dojo.toJson(). + // _indentStr: + // private variable for recursive calls when pretty printing, do not use. + // example: + // simple serialization of a trivial object + // | var jsonStr = dojo.toJson({ howdy: "stranger!", isStrange: true }); + // | doh.is('{"howdy":"stranger!","isStrange":true}', jsonStr); + // example: + // a custom serializer for an objects of a particular class: + // | dojo.declare("Furby", null, { + // | furbies: "are strange", + // | furbyCount: 10, + // | __json__: function(){ + // | }, + // | }); + + if(it === undefined){ + return "undefined"; + } + var objtype = typeof it; + if(objtype == "number" || objtype == "boolean"){ + return it + ""; + } + if(it === null){ + return "null"; + } + if(dojo.isString(it)){ + return dojo._escapeString(it); + } + // recurse + var recurse = arguments.callee; + // short-circuit for objects that support "json" serialization + // if they return "self" then just pass-through... + var newObj; + _indentStr = _indentStr || ""; + var nextIndent = prettyPrint ? _indentStr + dojo.toJsonIndentStr : ""; + var tf = it.__json__||it.json; + if(dojo.isFunction(tf)){ + newObj = tf.call(it); + if(it !== newObj){ + return recurse(newObj, prettyPrint, nextIndent); + } + } + if(it.nodeType && it.cloneNode){ // isNode + // we can't seriailize DOM nodes as regular objects because they have cycles + // DOM nodes could be serialized with something like outerHTML, but + // that can be provided by users in the form of .json or .__json__ function. + throw new Error("Can't serialize DOM nodes"); + } + + var sep = prettyPrint ? " " : ""; + var newLine = prettyPrint ? "\n" : ""; + + // array + if(dojo.isArray(it)){ + var res = dojo.map(it, function(obj){ + var val = recurse(obj, prettyPrint, nextIndent); + if(typeof val != "string"){ + val = "undefined"; + } + return newLine + nextIndent + val; + }); + return "[" + res.join("," + sep) + newLine + _indentStr + "]"; + } + /* + // look in the registry + try { + window.o = it; + newObj = dojo.json.jsonRegistry.match(it); + return recurse(newObj, prettyPrint, nextIndent); + }catch(e){ + // console.log(e); + } + // it's a function with no adapter, skip it + */ + if(objtype == "function"){ + return null; // null + } + // generic object code path + var output = [], key; + for(key in it){ + var keyStr, val; + if(typeof key == "number"){ + keyStr = '"' + key + '"'; + }else if(typeof key == "string"){ + keyStr = dojo._escapeString(key); + }else{ + // skip non-string or number keys + continue; + } + val = recurse(it[key], prettyPrint, nextIndent); + if(typeof val != "string"){ + // skip non-serializable values + continue; + } + // FIXME: use += on Moz!! + // MOW NOTE: using += is a pain because you have to account for the dangling comma... + output.push(newLine + nextIndent + keyStr + ":" + sep + val); + } + return "{" + output.join("," + sep) + newLine + _indentStr + "}"; // String } -return "{"+_e.join(","+_9)+_a+_4+"}"; -}; + } diff --git a/lib/dojo/_base/lang.js b/lib/dojo/_base/lang.js index e7721a35..0e9c7c2f 100644 --- a/lib/dojo/_base/lang.js +++ b/lib/dojo/_base/lang.js @@ -5,144 +5,388 @@ */ -if(!dojo._hasResource["dojo._base.lang"]){ -dojo._hasResource["dojo._base.lang"]=true; +if(!dojo._hasResource["dojo._base.lang"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.lang"] = true; dojo.provide("dojo._base.lang"); + (function(){ -var d=dojo,_1=Object.prototype.toString; -dojo.isString=function(it){ -return (typeof it=="string"||it instanceof String); -}; -dojo.isArray=function(it){ -return it&&(it instanceof Array||typeof it=="array"); -}; -dojo.isFunction=function(it){ -return _1.call(it)==="[object Function]"; -}; -dojo.isObject=function(it){ -return it!==undefined&&(it===null||typeof it=="object"||d.isArray(it)||d.isFunction(it)); -}; -dojo.isArrayLike=function(it){ -return it&&it!==undefined&&!d.isString(it)&&!d.isFunction(it)&&!(it.tagName&&it.tagName.toLowerCase()=="form")&&(d.isArray(it)||isFinite(it.length)); -}; -dojo.isAlien=function(it){ -return it&&!d.isFunction(it)&&/\{\s*\[native code\]\s*\}/.test(String(it)); -}; -dojo.extend=function(_2,_3){ -for(var i=1,l=arguments.length;i2){ -return d._hitchArgs.apply(d,arguments); -} -if(!_a){ -_a=_9; -_9=null; -} -if(d.isString(_a)){ -_9=_9||d.global; -if(!_9[_a]){ -throw (["dojo.hitch: scope[\"",_a,"\"] is null (scope=\"",_9,"\")"].join("")); -} -return function(){ -return _9[_a].apply(_9,arguments||[]); -}; -} -return !_9?_a:function(){ -return _a.apply(_9,arguments||[]); -}; -}; -dojo.delegate=dojo._delegate=(function(){ -function _b(){ -}; -return function(_c,_d){ -_b.prototype=_c; -var _e=new _b(); -_b.prototype=null; -if(_d){ -d._mixin(_e,_d); -} -return _e; -}; -})(); -var _f=function(obj,_10,_11){ -return (_11||[]).concat(Array.prototype.slice.call(obj,_10||0)); -}; -var _12=function(obj,_13,_14){ -var arr=_14||[]; -for(var x=_13||0;x 2){ + return d._hitchArgs.apply(d, arguments); // Function + } + if(!method){ + method = scope; + scope = null; + } + if(d.isString(method)){ + scope = scope || d.global; + if(!scope[method]){ throw(['dojo.hitch: scope["', method, '"] is null (scope="', scope, '")'].join('')); } + return function(){ return scope[method].apply(scope, arguments || []); }; // Function + } + return !scope ? method : function(){ return method.apply(scope, arguments || []); }; // Function + } + + /*===== + dojo.delegate = function(obj, props){ + // summary: + // Returns a new object which "looks" to obj for properties which it + // does not have a value for. Optionally takes a bag of properties to + // seed the returned object with initially. + // description: + // This is a small implementaton of the Boodman/Crockford delegation + // pattern in JavaScript. An intermediate object constructor mediates + // the prototype chain for the returned object, using it to delegate + // down to obj for property lookup when object-local lookup fails. + // This can be thought of similarly to ES4's "wrap", save that it does + // not act on types but rather on pure objects. + // obj: + // The object to delegate to for properties not found directly on the + // return object or in props. + // props: + // an object containing properties to assign to the returned object + // returns: + // an Object of anonymous type + // example: + // | var foo = { bar: "baz" }; + // | var thinger = dojo.delegate(foo, { thud: "xyzzy"}); + // | thinger.bar == "baz"; // delegated to foo + // | foo.thud == undefined; // by definition + // | thinger.thud == "xyzzy"; // mixed in from props + // | foo.bar = "thonk"; + // | thinger.bar == "thonk"; // still delegated to foo's bar + } + =====*/ + + dojo.delegate = dojo._delegate = (function(){ + // boodman/crockford delegation w/ cornford optimization + function TMP(){} + return function(obj, props){ + TMP.prototype = obj; + var tmp = new TMP(); + TMP.prototype = null; + if(props){ + d._mixin(tmp, props); + } + return tmp; // Object + } + })(); + + /*===== + dojo._toArray = function(obj, offset, startWith){ + // summary: + // Converts an array-like object (i.e. arguments, DOMCollection) to an + // array. Returns a new Array with the elements of obj. + // obj: Object + // the object to "arrayify". We expect the object to have, at a + // minimum, a length property which corresponds to integer-indexed + // properties. + // offset: Number? + // the location in obj to start iterating from. Defaults to 0. + // Optional. + // startWith: Array? + // An array to pack with the properties of obj. If provided, + // properties in obj are appended at the end of startWith and + // startWith is the returned array. + } + =====*/ + + var efficient = function(obj, offset, startWith){ + return (startWith||[]).concat(Array.prototype.slice.call(obj, offset||0)); + }; + + var slow = function(obj, offset, startWith){ + var arr = startWith||[]; + for(var x = offset || 0; x < obj.length; x++){ + arr.push(obj[x]); + } + return arr; + }; + + dojo._toArray = + d.isIE ? function(obj){ + return ((obj.item) ? slow : efficient).apply(this, arguments); + } : + efficient; + + dojo.partial = function(/*Function|String*/method /*, ...*/){ + // summary: + // similar to hitch() except that the scope object is left to be + // whatever the execution context eventually becomes. + // description: + // Calling dojo.partial is the functional equivalent of calling: + // | dojo.hitch(null, funcName, ...); + var arr = [ null ]; + return d.hitch.apply(d, arr.concat(d._toArray(arguments))); // Function + } + + var extraNames = d._extraNames, extraLen = extraNames.length, empty = {}; + + dojo.clone = function(/*anything*/ o){ + // summary: + // Clones objects (including DOM nodes) and all children. + // Warning: do not clone cyclic structures. + if(!o || typeof o != "object" || d.isFunction(o)){ + // null, undefined, any non-object, or function + return o; // anything + } + if(o.nodeType && "cloneNode" in o){ + // DOM Node + return o.cloneNode(true); // Node + } + if(o instanceof Date){ + // Date + return new Date(o.getTime()); // Date + } + var r, i, l, s, name; + if(d.isArray(o)){ + // array + r = []; + for(i = 0, l = o.length; i < l; ++i){ + if(i in o){ + r.push(d.clone(o[i])); + } + } +// we don't clone functions for performance reasons +// }else if(d.isFunction(o)){ +// // function +// r = function(){ return o.apply(this, arguments); }; + }else{ + // generic objects + r = o.constructor ? new o.constructor() : {}; + } + for(name in o){ + // the "tobj" condition avoid copying properties in "source" + // inherited from Object.prototype. For example, if target has a custom + // toString() method, don't overwrite it with the toString() method + // that source inherited from Object.prototype + s = o[name]; + if(!(name in r) || (r[name] !== s && (!(name in empty) || empty[name] !== s))){ + r[name] = d.clone(s); + } + } + // IE doesn't recognize some custom functions in for..in + if(extraLen){ + for(i = 0; i < extraLen; ++i){ + name = extraNames[i]; + s = o[name]; + if(!(name in r) || (r[name] !== s && (!(name in empty) || empty[name] !== s))){ + r[name] = s; // functions only, we don't clone them + } + } + } + return r; // Object + } + + /*===== + dojo.trim = function(str){ + // summary: + // Trims whitespace from both sides of the string + // str: String + // String to be trimmed + // returns: String + // Returns the trimmed string + // description: + // This version of trim() was selected for inclusion into the base due + // to its compact size and relatively good performance + // (see [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript) + // Uses String.prototype.trim instead, if available. + // The fastest but longest version of this function is located at + // dojo.string.trim() + return ""; // String + } + =====*/ + + dojo.trim = String.prototype.trim ? + function(str){ return str.trim(); } : + function(str){ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); }; + + /*===== + dojo.replace = function(tmpl, map, pattern){ + // summary: + // Performs parameterized substitutions on a string. Throws an + // exception if any parameter is unmatched. + // tmpl: String + // String to be used as a template. + // map: Object|Function + // If an object, it is used as a dictionary to look up substitutions. + // If a function, it is called for every substitution with following + // parameters: a whole match, a name, an offset, and the whole template + // string (see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/String/replace + // for more details). + // pattern: RegEx? + // Optional regular expression objects that overrides the default pattern. + // Must be global and match one item. The default is: /\{([^\}]+)\}/g, + // which matches patterns like that: "{xxx}", where "xxx" is any sequence + // of characters, which doesn't include "}". + // returns: String + // Returns the substituted string. + // example: + // | // uses a dictionary for substitutions: + // | dojo.replace("Hello, {name.first} {name.last} AKA {nick}!", + // | { + // | nick: "Bob", + // | name: { + // | first: "Robert", + // | middle: "X", + // | last: "Cringely" + // | } + // | }); + // | // returns: Hello, Robert Cringely AKA Bob! + // example: + // | // uses an array for substitutions: + // | dojo.replace("Hello, {0} {2}!", + // | ["Robert", "X", "Cringely"]); + // | // returns: Hello, Robert Cringely! + // example: + // | // uses a function for substitutions: + // | function sum(a){ + // | var t = 0; + // | dojo.forEach(a, function(x){ t += x; }); + // | return t; + // | } + // | dojo.replace( + // | "{count} payments averaging {avg} USD per payment.", + // | dojo.hitch( + // | { payments: [11, 16, 12] }, + // | function(_, key){ + // | switch(key){ + // | case "count": return this.payments.length; + // | case "min": return Math.min.apply(Math, this.payments); + // | case "max": return Math.max.apply(Math, this.payments); + // | case "sum": return sum(this.payments); + // | case "avg": return sum(this.payments) / this.payments.length; + // | } + // | } + // | ) + // | ); + // | // prints: 3 payments averaging 13 USD per payment. + // example: + // | // uses an alternative PHP-like pattern for substitutions: + // | dojo.replace("Hello, ${0} ${2}!", + // | ["Robert", "X", "Cringely"], /\$\{([^\}]+)\}/g); + // | // returns: Hello, Robert Cringely! + return ""; // String + } + =====*/ + + var _pattern = /\{([^\}]+)\}/g; + dojo.replace = function(tmpl, map, pattern){ + return tmpl.replace(pattern || _pattern, d.isFunction(map) ? + map : function(_, k){ return d.getObject(k, false, map); }); + }; })(); + } diff --git a/lib/dojo/_base/query-sizzle.js b/lib/dojo/_base/query-sizzle.js index ac26c181..5d160ec5 100644 --- a/lib/dojo/_base/query-sizzle.js +++ b/lib/dojo/_base/query-sizzle.js @@ -5,624 +5,865 @@ */ -if(!dojo._hasResource["dojo._base.query"]){ -dojo._hasResource["dojo._base.query"]=true; -if(typeof dojo!="undefined"){ -dojo.provide("dojo._base.query"); -dojo.require("dojo._base.NodeList"); -dojo.query=function(_1,_2,_3){ -_3=_3||dojo.NodeList; -if(!_1){ -return new _3(); -} -if(_1.constructor==_3){ -return _1; -} -if(!dojo.isString(_1)){ -return new _3(_1); -} -if(dojo.isString(_2)){ -_2=dojo.byId(_2); -if(!_2){ -return new _3(); -} -} -return dojo.Sizzle(_1,_2,new _3()); -}; -dojo._filterQueryResult=function(_4,_5){ -return dojo.Sizzle.filter(_5,_4); -}; -} -(function(ns){ -var _6=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,_7=0,_8=Object.prototype.toString; -var _9=function(_a,_b,_c,_d){ -_c=_c||[]; -_b=_b||document; -if(_b.nodeType!==1&&_b.nodeType!==9){ -return []; -} -if(!_a||typeof _a!=="string"){ -return _c; -} -var _e=[],m,_f,_10,_11,_12,_13,_14=true; -_6.lastIndex=0; -while((m=_6.exec(_a))!==null){ -_e.push(m[1]); -if(m[2]){ -_13=RegExp.rightContext; -break; -} -} -if(_e.length>1&&_15.match.POS.exec(_a)){ -if(_e.length===2&&_15.relative[_e[0]]){ -var _16="",_17; -while((_17=_15.match.POS.exec(_a))){ -_16+=_17[0]; -_a=_a.replace(_15.match.POS,""); -} -_f=_9.filter(_16,_9(_a,_b)); -}else{ -_f=_15.relative[_e[0]]?[_b]:_9(_e.shift(),_b); -while(_e.length){ -var _18=[]; -_a=_e.shift(); -if(_15.relative[_a]){ -_a+=_e.shift(); -} -for(var i=0,l=_f.length;i0){ -_10=_19(_f); -}else{ -_14=false; -} -while(_e.length){ -var cur=_e.pop(),pop=cur; -if(!_15.relative[cur]){ -cur=""; -}else{ -pop=_e.pop(); -} -if(pop==null){ -pop=_b; -} -_15.relative[cur](_10,pop); -} -} -if(!_10){ -_10=_f; -} -if(!_10){ -throw "Syntax error, unrecognized expression: "+(cur||_a); -} -if(_8.call(_10)==="[object Array]"){ -if(!_14){ -_c.push.apply(_c,_10); -}else{ -if(_b.nodeType===1){ -for(var i=0;_10[i]!=null;i++){ -if(_10[i]&&(_10[i]===true||_10[i].nodeType===1&&_1a(_b,_10[i]))){ -_c.push(_f[i]); -} -} -}else{ -for(var i=0;_10[i]!=null;i++){ -if(_10[i]&&_10[i].nodeType===1){ -_c.push(_f[i]); -} -} -} -} -}else{ -_19(_10,_c); -} -if(_13){ -_9(_13,_b,_c,_d); +if(!dojo._hasResource["dojo._base.query"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.query"] = true; +/*! + * Sizzle CSS Selector Engine - v0.9 + * Copyright 2009, John Resig + * Redistributed with the Dojo Toolkit under the terms of the New BSD license. + * More information: http://sizzlejs.com/ + * + * This version from github, dated 1/23/2009, commit: e374a73bbffc12ec3b5f252e7f76e593c508dfa5 + * Modified for dojo loader, and to fit into dojo namespace. This was done by passing + * dojo object to anonymous function, then assigning Sizzle to dojo.Sizzle instead of window.Sizzle. + * Then an alias for dojo.query and dojo._filterQueryResult(). dojo.psuedos is not mapped. + * Finally, dojo.provide/require added. + */ + +//This file gets copied to dojo/_base/query.js, so set the provide accordingly. +if(typeof dojo != "undefined"){ + dojo.provide("dojo._base.query"); + dojo.require("dojo._base.NodeList"); + + //Start Dojo mappings. + dojo.query = function(/*String*/ query, /*String|DOMNode?*/ root, /*Function?*/listCtor){ + listCtor = listCtor || dojo.NodeList; + + if(!query){ + return new listCtor(); + } + + if(query.constructor == listCtor){ + return query; + } + if(!dojo.isString(query)){ + return new listCtor(query); // dojo.NodeList + } + if(dojo.isString(root)){ + root = dojo.byId(root); + if(!root){ return new listCtor(); } + } + + return dojo.Sizzle(query, root, new listCtor()); + } + + dojo._filterQueryResult = function(nodeList, simpleFilter){ + return dojo.Sizzle.filter(simpleFilter, nodeList); + } } -return _c; + +//Main Sizzle code follows... +//ns argument, added for dojo, used at the end of the file. +;(function(ns){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|[^[\]]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g, + done = 0, + toString = Object.prototype.toString; + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) + return []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true; + + // Reset the position of the chunker regexp (start from head) + chunker.lastIndex = 0; + + while ( (m = chunker.exec(selector)) !== null ) { + parts.push( m[1] ); + + if ( m[2] ) { + extra = RegExp.rightContext; + break; + } + } + + if ( parts.length > 1 && Expr.match.POS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + var later = "", match; + + // Position selectors must be done after the filter + while ( (match = Expr.match.POS.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.POS, "" ); + } + + set = Sizzle.filter( later, Sizzle( selector, context ) ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + var tmpSet = []; + + selector = parts.shift(); + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + for ( var i = 0, l = set.length; i < l; i++ ) { + Sizzle( selector, set[i], tmpSet ); + } + + set = tmpSet; + } + } + } else { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context ); + set = Sizzle.filter( ret.expr, ret.set ); + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop ); + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, context, results, seed ); + } + + return results; }; -_9.matches=function(_1b,set){ -return _9(_1b,null,null,set); + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); }; -_9.find=function(_1c,_1d){ -var set,_1e; -if(!_1c){ -return []; -} -for(var i=0,l=_15.order.length;i":function(_33,_34){ -if(typeof _34==="string"&&!/\W/.test(_34)){ -_34=_34.toUpperCase(); -for(var i=0,l=_33.length;i=0){ -if(!_4b){ -_4c.push(_4a[i]); -} -}else{ -if(_4b){ -_4a[i]=false; -} -} -} -return false; -},ID:function(_4d){ -return _4d[1]; -},TAG:function(_4e){ -return _4e[1].toUpperCase(); -},CHILD:function(_4f){ -if(_4f[1]=="nth"){ -var _50=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(_4f[2]=="even"&&"2n"||_4f[2]=="odd"&&"2n+1"||!/\D/.test(_4f[2])&&"0n+"+_4f[2]||_4f[2]); -_4f[2]=(_50[1]+(_50[2]||1))-0; -_4f[3]=_50[3]-0; -} -_4f[0]="done"+(_7++); -return _4f; -},ATTR:function(_51){ -var _52=_51[1]; -if(_15.attrMap[_52]){ -_51[1]=_15.attrMap[_52]; -} -if(_51[2]==="~="){ -_51[4]=" "+_51[4]+" "; -} -return _51; -},PSEUDO:function(_53,_54,_55,_56,not){ -if(_53[1]==="not"){ -if(_53[3].match(_6).length>1){ -_53[3]=_9(_53[3],null,null,_54); -}else{ -var ret=_9.filter(_53[3],_54,_55,true^not); -if(!_55){ -_56.push.apply(_56,ret); -} -return false; -} -} -return _53; -},POS:function(_57){ -_57.unshift(true); -return _57; -}},filters:{enabled:function(_58){ -return _58.disabled===false&&_58.type!=="hidden"; -},disabled:function(_59){ -return _59.disabled===true; -},checked:function(_5a){ -return _5a.checked===true; -},selected:function(_5b){ -_5b.parentNode.selectedIndex; -return _5b.selected===true; -},parent:function(_5c){ -return !!_5c.firstChild; -},empty:function(_5d){ -return !_5d.firstChild; -},has:function(_5e,i,_5f){ -return !!_9(_5f[3],_5e).length; -},header:function(_60){ -return /h\d/i.test(_60.nodeName); -},text:function(_61){ -return "text"===_61.type; -},radio:function(_62){ -return "radio"===_62.type; -},checkbox:function(_63){ -return "checkbox"===_63.type; -},file:function(_64){ -return "file"===_64.type; -},password:function(_65){ -return "password"===_65.type; -},submit:function(_66){ -return "submit"===_66.type; -},image:function(_67){ -return "image"===_67.type; -},reset:function(_68){ -return "reset"===_68.type; -},button:function(_69){ -return "button"===_69.type||_69.nodeName.toUpperCase()==="BUTTON"; -},input:function(_6a){ -return /input|select|textarea|button/i.test(_6a.nodeName); -}},setFilters:{first:function(_6b,i){ -return i===0; -},last:function(_6c,i,_6d,_6e){ -return i===_6e.length-1; -},even:function(_6f,i){ -return i%2===0; -},odd:function(_70,i){ -return i%2===1; -},lt:function(_71,i,_72){ -return i<_72[3]-0; -},gt:function(_73,i,_74){ -return i>_74[3]-0; -},nth:function(_75,i,_76){ -return _76[3]-0==i; -},eq:function(_77,i,_78){ -return _78[3]-0==i; -}},filter:{CHILD:function(_79,_7a){ -var _7b=_7a[1],_7c=_79.parentNode; -var _7d=_7a[0]; -if(_7c&&!_7c[_7d]){ -var _7e=1; -for(var _7f=_7c.firstChild;_7f;_7f=_7f.nextSibling){ -if(_7f.nodeType==1){ -_7f.nodeIndex=_7e++; -} -} -_7c[_7d]=_7e-1; -} -if(_7b=="first"){ -return _79.nodeIndex==1; -}else{ -if(_7b=="last"){ -return _79.nodeIndex==_7c[_7d]; -}else{ -if(_7b=="only"){ -return _7c[_7d]==1; -}else{ -if(_7b=="nth"){ -var add=false,_80=_7a[2],_81=_7a[3]; -if(_80==1&&_81==0){ -return true; -} -if(_80==0){ -if(_79.nodeIndex==_81){ -add=true; -} -}else{ -if((_79.nodeIndex-_81)%_80==0&&(_79.nodeIndex-_81)/_80>=0){ -add=true; -} -} -return add; -} -} -} -} -},PSEUDO:function(_82,_83,i,_84){ -var _85=_83[1],_86=_15.filters[_85]; -if(_86){ -return _86(_82,i,_83,_84); -}else{ -if(_85==="contains"){ -return (_82.textContent||_82.innerText||"").indexOf(_83[3])>=0; -}else{ -if(_85==="not"){ -var not=_83[3]; -for(var i=0,l=not.length;i=0:_91==="~="?(" "+_90+" ").indexOf(_92)>=0:!_8e[4]?_8f:_91==="!="?_90!=_92:_91==="^="?_90.indexOf(_92)===0:_91==="$="?_90.substr(_90.length-_92.length)===_92:_91==="|="?_90===_92||_90.substr(0,_92.length+1)===_92+"-":false; -},POS:function(_93,_94,i,_95){ -var _96=_94[2],_97=_15.setFilters[_96]; -if(_97){ -return _97(_93,i,_94,_95); -} -}}}; -for(var _98 in _15.match){ -_15.match[_98]=RegExp(_15.match[_98].source+/(?![^\[]*\])(?![^\(]*\))/.source); -} -var _19=function(_99,_9a){ -_99=Array.prototype.slice.call(_99); -if(_9a){ -_9a.push.apply(_9a,_99); -return _9a; -} -return _99; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u0128-\uFFFF_-]|\\.)+)/, + CLASS: /\.((?:[\w\u0128-\uFFFF_-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u0128-\uFFFF_-]|\\.)+)['"]*\]/, + ATTR: /\[((?:[\w\u0128-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\]/, + TAG: /^((?:[\w\u0128-\uFFFF\*_-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child\(?(even|odd|[\dn+-]*)\)?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)\(?(\d*)\)?(?:[^-]|$)/, + PSEUDO: /:((?:[\w\u0128-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + relative: { + "+": function(checkSet, part){ + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var cur = elem.previousSibling; + while ( cur && cur.nodeType !== 1 ) { + cur = cur.previousSibling; + } + checkSet[i] = typeof part === "string" ? + cur || false : + cur === part; + } + } + + if ( typeof part === "string" ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = typeof part === "string" ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( typeof part === "string" ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part){ + var doneName = "done" + (done++), checkFn = dirCheck; + + if ( !part.match(/\W/) ) { + var nodeCheck = part = part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck); + }, + "~": function(checkSet, part){ + var doneName = "done" + (done++), checkFn = dirCheck; + + if ( typeof part === "string" && !part.match(/\W/) ) { + var nodeCheck = part = part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck); + } + }, + find: { + ID: function(match, context){ + if ( context.getElementById ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + return context.getElementsByName ? context.getElementsByName(match[1]) : null; + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not){ + match = " " + match[1].replace(/\\/g, "") + " "; + + for ( var i = 0; curLoop[i]; i++ ) { + if ( not ^ (" " + curLoop[i].className + " ").indexOf(match) >= 0 ) { + if ( !inplace ) + result.push( curLoop[i] ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + + return false; + }, + ID: function(match){ + return match[1]; + }, + TAG: function(match){ + return match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = "done" + (done++); + + return match; + }, + ATTR: function(match){ + var name = match[1]; + + if ( Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( match[3].match(chunker).length > 1 ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + CHILD: function(elem, match){ + var type = match[1], parent = elem.parentNode; + + var doneName = match[0]; + + if ( parent && !parent[ doneName ] ) { + var count = 1; + + for ( var node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType == 1 ) { + node.nodeIndex = count++; + } + } + + parent[ doneName ] = count - 1; + } + + if ( type == "first" ) { + return elem.nodeIndex == 1; + } else if ( type == "last" ) { + return elem.nodeIndex == parent[ doneName ]; + } else if ( type == "only" ) { + return parent[ doneName ] == 1; + } else if ( type == "nth" ) { + var add = false, first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + if ( first == 0 ) { + if ( elem.nodeIndex == last ) { + add = true; + } + } else if ( (elem.nodeIndex - last) % first == 0 && (elem.nodeIndex - last) / first >= 0 ) { + add = true; + } + + return add; + } + }, + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return match.test( elem.className ); + }, + ATTR: function(elem, match){ + var result = elem[ match[1] ] || elem.getAttribute( match[1] ), value = result + "", type = match[2], check = match[4]; + return result == null ? + false : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !match[4] ? + result : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } }; -try{ -Array.prototype.slice.call(document.documentElement.childNodes); -} -catch(e){ -_19=function(_9b,_9c){ -var ret=_9c||[]; -if(_8.call(_9b)==="[object Array]"){ -Array.prototype.push.apply(ret,_9b); -}else{ -if(typeof _9b.length==="number"){ -for(var i=0,l=_9b.length;i"; -var _9e=document.documentElement; -_9e.insertBefore(_9d,_9e.firstChild); -if(!!document.getElementById(id)){ -_15.find.ID=function(_9f,_a0){ -if(_a0.getElementById){ -var m=_a0.getElementById(_9f[1]); -return m?m.id===_9f[1]||m.getAttributeNode&&m.getAttributeNode("id").nodeValue===_9f[1]?[m]:undefined:[]; -} -}; -_15.filter.ID=function(_a1,_a2){ -var _a3=_a1.getAttributeNode&&_a1.getAttributeNode("id"); -return _a1.nodeType===1&&_a3&&_a3.nodeValue===_a2; -}; -} -_9e.removeChild(_9d); + // We're going to inject a fake input element with a specified name + var form = document.createElement("form"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context){ + if ( context.getElementById ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || m.getAttributeNode && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = elem.getAttributeNode && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); })(); + +// Check to see if the browser returns only elements +// when doing getElementsByTagName("*") (function(){ -var div=document.createElement("div"); -div.appendChild(document.createComment("")); -if(div.getElementsByTagName("*").length>0){ -_15.find.TAG=function(_a4,_a5){ -var _a6=_a5.getElementsByTagName(_a4[1]); -if(_a4[1]==="*"){ -var tmp=[]; -for(var i=0;_a6[i];i++){ -if(_a6[i].nodeType===1){ -tmp.push(_a6[i]); -} -} -_a6=tmp; -} -return _a6; -}; -} + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } })(); -if(document.querySelectorAll){ -(function(){ -var _a7=_9; -_9=function(_a8,_a9,_aa,_ab){ -_a9=_a9||document; -if(!_ab&&_a9.nodeType===9){ -try{ -return _19(_a9.querySelectorAll(_a8),_aa); -} -catch(e){ -} -} -return _a7(_a8,_a9,_aa,_ab); -}; -_9.find=_a7.find; -_9.filter=_a7.filter; -_9.selectors=_a7.selectors; -_9.matches=_a7.matches; + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle; + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + if ( !seed && context.nodeType === 9 ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + Sizzle.find = oldSizzle.find; + Sizzle.filter = oldSizzle.filter; + Sizzle.selectors = oldSizzle.selectors; + Sizzle.matches = oldSizzle.matches; })(); + +if ( document.documentElement.getElementsByClassName ) { + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context) { + return context.getElementsByClassName(match[1]); + }; } -if(document.documentElement.getElementsByClassName){ -_15.order.splice(1,0,"CLASS"); -_15.find.CLASS=function(_ac,_ad){ -return _ad.getElementsByClassName(_ac[1]); -}; -} -function _3d(dir,cur,_ae,_af,_b0){ -for(var i=0,l=_af.length;i0){ -_b8=_b7; -break; -} -} -} -_b7=_b7[dir]; -} -_b5[i]=_b8; + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem && elem.nodeType ) { + var done = elem[doneName]; + if ( done ) { + match = checkSet[ done ]; + break; + } + + if ( elem.nodeType === 1 ) + elem[doneName] = i; + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } } + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem && elem.nodeType ) { + if ( elem[doneName] ) { + match = checkSet[ elem[doneName] ]; + break; + } + + if ( elem.nodeType === 1 ) { + elem[doneName] = i; + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } } + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); }; -var _1a=document.compareDocumentPosition?function(a,b){ -return a.compareDocumentPosition(b)&16; -}:function(a,b){ -return a!==b&&(a.contains?a.contains(b):true); -}; -(ns||window).Sizzle=_9; -})(typeof dojo=="undefined"?null:dojo); + +// EXPOSE + +(ns || window).Sizzle = Sizzle; + +})(typeof dojo == "undefined" ? null : dojo); + } diff --git a/lib/dojo/_base/query.js b/lib/dojo/_base/query.js index ecf84682..7b9878e4 100644 --- a/lib/dojo/_base/query.js +++ b/lib/dojo/_base/query.js @@ -5,793 +5,1520 @@ */ -if(!dojo._hasResource["dojo._base.query"]){ -dojo._hasResource["dojo._base.query"]=true; -if(typeof dojo!="undefined"){ -dojo.provide("dojo._base.query"); -dojo.require("dojo._base.NodeList"); -dojo.require("dojo._base.lang"); -} -(function(d){ -var _1=d.trim; -var _2=d.forEach; -var _3=d._NodeListCtor=d.NodeList; -var _4=function(){ -return d.doc; -}; -var _5=((d.isWebKit||d.isMozilla)&&((_4().compatMode)=="BackCompat")); -var _6=!!_4().firstChild["children"]?"children":"childNodes"; -var _7=">~+"; -var _8=false; -var _9=function(){ -return true; -}; -var _a=function(_b){ -if(_7.indexOf(_b.slice(-1))>=0){ -_b+=" * "; -}else{ -_b+=" "; -} -var ts=function(s,e){ -return _1(_b.slice(s,e)); -}; -var _c=[]; -var _d=-1,_e=-1,_f=-1,_10=-1,_11=-1,_12=-1,_13=-1,lc="",cc="",_14; -var x=0,ql=_b.length,_15=null,_16=null; -var _17=function(){ -if(_13>=0){ -var tv=(_13==x)?null:ts(_13,x); -_15[(_7.indexOf(tv)<0)?"tag":"oper"]=tv; -_13=-1; -} -}; -var _18=function(){ -if(_12>=0){ -_15.id=ts(_12,x).replace(/\\/g,""); -_12=-1; -} -}; -var _19=function(){ -if(_11>=0){ -_15.classes.push(ts(_11+1,x).replace(/\\/g,"")); -_11=-1; -} -}; -var _1a=function(){ -_18(); -_17(); -_19(); -}; -var _1b=function(){ -_1a(); -if(_10>=0){ -_15.pseudos.push({name:ts(_10+1,x)}); -} -_15.loops=(_15.pseudos.length||_15.attrs.length||_15.classes.length); -_15.oquery=_15.query=ts(_14,x); -_15.otag=_15.tag=(_15["oper"])?null:(_15.tag||"*"); -if(_15.tag){ -_15.tag=_15.tag.toUpperCase(); -} -if(_c.length&&(_c[_c.length-1].oper)){ -_15.infixOper=_c.pop(); -_15.query=_15.infixOper.query+" "+_15.query; -} -_c.push(_15); -_15=null; -}; -for(;lc=cc,cc=_b.charAt(x),x=0){ -if(cc=="]"){ -if(!_16.attr){ -_16.attr=ts(_d+1,x); -}else{ -_16.matchFor=ts((_f||_d+1),x); -} -var cmf=_16.matchFor; -if(cmf){ -if((cmf.charAt(0)=="\"")||(cmf.charAt(0)=="'")){ -_16.matchFor=cmf.slice(1,-1); -} -} -_15.attrs.push(_16); -_16=null; -_d=_f=-1; -}else{ -if(cc=="="){ -var _1c=("|~^$*".indexOf(lc)>=0)?lc:""; -_16.type=_1c+cc; -_16.attr=ts(_d+1,x-_1c.length); -_f=x+1; -} -} -}else{ -if(_e>=0){ -if(cc==")"){ -if(_10>=0){ -_16.value=ts(_e+1,x); -} -_10=_e=-1; -} -}else{ -if(cc=="#"){ -_1a(); -_12=x+1; -}else{ -if(cc=="."){ -_1a(); -_11=x; -}else{ -if(cc==":"){ -_1a(); -_10=x; -}else{ -if(cc=="["){ -_1a(); -_d=x; -_16={}; -}else{ -if(cc=="("){ -if(_10>=0){ -_16={name:ts(_10+1,x),value:null}; -_15.pseudos.push(_16); -} -_e=x; -}else{ -if((cc==" ")&&(lc!=cc)){ -_1b(); -} -} -} -} -} -} -} -} -} -return _c; -}; -var _1d=function(_1e,_1f){ -if(!_1e){ -return _1f; -} -if(!_1f){ -return _1e; -} -return function(){ -return _1e.apply(window,arguments)&&_1f.apply(window,arguments); -}; -}; -var _20=function(i,arr){ -var r=arr||[]; -if(i){ -r.push(i); -} -return r; -}; -var _21=function(n){ -return (1==n.nodeType); -}; -var _22=""; -var _23=function(_24,_25){ -if(!_24){ -return _22; -} -if(_25=="class"){ -return _24.className||_22; -} -if(_25=="for"){ -return _24.htmlFor||_22; -} -if(_25=="style"){ -return _24.style.cssText||_22; -} -return (_8?_24.getAttribute(_25):_24.getAttribute(_25,2))||_22; -}; -var _26={"*=":function(_27,_28){ -return function(_29){ -return (_23(_29,_27).indexOf(_28)>=0); -}; -},"^=":function(_2a,_2b){ -return function(_2c){ -return (_23(_2c,_2a).indexOf(_2b)==0); -}; -},"$=":function(_2d,_2e){ -var _2f=" "+_2e; -return function(_30){ -var ea=" "+_23(_30,_2d); -return (ea.lastIndexOf(_2e)==(ea.length-_2e.length)); -}; -},"~=":function(_31,_32){ -var _33=" "+_32+" "; -return function(_34){ -var ea=" "+_23(_34,_31)+" "; -return (ea.indexOf(_33)>=0); -}; -},"|=":function(_35,_36){ -var _37=" "+_36+"-"; -return function(_38){ -var ea=" "+_23(_38,_35); -return ((ea==_36)||(ea.indexOf(_37)==0)); -}; -},"=":function(_39,_3a){ -return function(_3b){ -return (_23(_3b,_39)==_3a); -}; -}}; -var _3c=(typeof _4().firstChild.nextElementSibling=="undefined"); -var _3d=!_3c?"nextElementSibling":"nextSibling"; -var _3e=!_3c?"previousElementSibling":"previousSibling"; -var _3f=(_3c?_21:_9); -var _40=function(_41){ -while(_41=_41[_3e]){ -if(_3f(_41)){ -return false; -} -} -return true; -}; -var _42=function(_43){ -while(_43=_43[_3d]){ -if(_3f(_43)){ -return false; -} -} -return true; -}; -var _44=function(_45){ -var _46=_45.parentNode; -var i=0,_47=_46[_6],ci=(_45["_i"]||-1),cl=(_46["_l"]||-1); -if(!_47){ -return -1; -} -var l=_47.length; -if(cl==l&&ci>=0&&cl>=0){ -return ci; -} -_46["_l"]=l; -ci=-1; -for(var te=_46["firstElementChild"]||_46["firstChild"];te;te=te[_3d]){ -if(_3f(te)){ -te["_i"]=++i; -if(_45===te){ -ci=i; -} -} -} -return ci; -}; -var _48=function(_49){ -return !((_44(_49))%2); -}; -var _4a=function(_4b){ -return ((_44(_4b))%2); -}; -var _4c={"checked":function(_4d,_4e){ -return function(_4f){ -return !!("checked" in _4f?_4f.checked:_4f.selected); -}; -},"first-child":function(){ -return _40; -},"last-child":function(){ -return _42; -},"only-child":function(_50,_51){ -return function(_52){ -if(!_40(_52)){ -return false; -} -if(!_42(_52)){ -return false; -} -return true; -}; -},"empty":function(_53,_54){ -return function(_55){ -var cn=_55.childNodes; -var cnl=_55.childNodes.length; -for(var x=cnl-1;x>=0;x--){ -var nt=cn[x].nodeType; -if((nt===1)||(nt==3)){ -return false; -} -} -return true; -}; -},"contains":function(_56,_57){ -var cz=_57.charAt(0); -if(cz=="\""||cz=="'"){ -_57=_57.slice(1,-1); -} -return function(_58){ -return (_58.innerHTML.indexOf(_57)>=0); -}; -},"not":function(_59,_5a){ -var p=_a(_5a)[0]; -var _5b={el:1}; -if(p.tag!="*"){ -_5b.tag=1; -} -if(!p.classes.length){ -_5b.classes=1; -} -var ntf=_5c(p,_5b); -return function(_5d){ -return (!ntf(_5d)); -}; -},"nth-child":function(_5e,_5f){ -var pi=parseInt; -if(_5f=="odd"){ -return _4a; -}else{ -if(_5f=="even"){ -return _48; -} -} -if(_5f.indexOf("n")!=-1){ -var _60=_5f.split("n",2); -var _61=_60[0]?((_60[0]=="-")?-1:pi(_60[0])):1; -var idx=_60[1]?pi(_60[1]):0; -var lb=0,ub=-1; -if(_61>0){ -if(idx<0){ -idx=(idx%_61)&&(_61+(idx%_61)); -}else{ -if(idx>0){ -if(idx>=_61){ -lb=idx-idx%_61; -} -idx=idx%_61; -} -} -}else{ -if(_61<0){ -_61*=-1; -if(idx>0){ -ub=idx; -idx=idx%_61; -} -} -} -if(_61>0){ -return function(_62){ -var i=_44(_62); -return (i>=lb)&&(ub<0||i<=ub)&&((i%_61)==idx); -}; -}else{ -_5f=idx; -} -} -var _63=pi(_5f); -return function(_64){ -return (_44(_64)==_63); -}; -}}; -var _65=(d.isIE)?function(_66){ -var clc=_66.toLowerCase(); -if(clc=="class"){ -_66="className"; -} -return function(_67){ -return (_8?_67.getAttribute(_66):_67[_66]||_67[clc]); -}; -}:function(_68){ -return function(_69){ -return (_69&&_69.getAttribute&&_69.hasAttribute(_68)); -}; -}; -var _5c=function(_6a,_6b){ -if(!_6a){ -return _9; -} -_6b=_6b||{}; -var ff=null; -if(!("el" in _6b)){ -ff=_1d(ff,_21); -} -if(!("tag" in _6b)){ -if(_6a.tag!="*"){ -ff=_1d(ff,function(_6c){ -return (_6c&&(_6c.tagName==_6a.getTag())); -}); -} -} -if(!("classes" in _6b)){ -_2(_6a.classes,function(_6d,idx,arr){ -var re=new RegExp("(?:^|\\s)"+_6d+"(?:\\s|$)"); -ff=_1d(ff,function(_6e){ -return re.test(_6e.className); -}); -ff.count=idx; -}); -} -if(!("pseudos" in _6b)){ -_2(_6a.pseudos,function(_6f){ -var pn=_6f.name; -if(_4c[pn]){ -ff=_1d(ff,_4c[pn](pn,_6f.value)); -} -}); -} -if(!("attrs" in _6b)){ -_2(_6a.attrs,function(_70){ -var _71; -var a=_70.attr; -if(_70.type&&_26[_70.type]){ -_71=_26[_70.type](a,_70.matchFor); -}else{ -if(a.length){ -_71=_65(a); -} -} -if(_71){ -ff=_1d(ff,_71); -} -}); -} -if(!("id" in _6b)){ -if(_6a.id){ -ff=_1d(ff,function(_72){ -return (!!_72&&(_72.id==_6a.id)); -}); -} -} -if(!ff){ -if(!("default" in _6b)){ -ff=_9; -} -} -return ff; -}; -var _73=function(_74){ -return function(_75,ret,bag){ -while(_75=_75[_3d]){ -if(_3c&&(!_21(_75))){ -continue; -} -if((!bag||_76(_75,bag))&&_74(_75)){ -ret.push(_75); -} -break; -} -return ret; -}; -}; -var _77=function(_78){ -return function(_79,ret,bag){ -var te=_79[_3d]; -while(te){ -if(_3f(te)){ -if(bag&&!_76(te,bag)){ -break; -} -if(_78(te)){ -ret.push(te); -} -} -te=te[_3d]; -} -return ret; -}; -}; -var _7a=function(_7b){ -_7b=_7b||_9; -return function(_7c,ret,bag){ -var te,x=0,_7d=_7c[_6]; -while(te=_7d[x++]){ -if(_3f(te)&&(!bag||_76(te,bag))&&(_7b(te,x))){ -ret.push(te); -} -} -return ret; -}; -}; -var _7e=function(_7f,_80){ -var pn=_7f.parentNode; -while(pn){ -if(pn==_80){ -break; -} -pn=pn.parentNode; -} -return !!pn; -}; -var _81={}; -var _82=function(_83){ -var _84=_81[_83.query]; -if(_84){ -return _84; -} -var io=_83.infixOper; -var _85=(io?io.oper:""); -var _86=_5c(_83,{el:1}); -var qt=_83.tag; -var _87=("*"==qt); -var ecs=_4()["getElementsByClassName"]; -if(!_85){ -if(_83.id){ -_86=(!_83.loops&&_87)?_9:_5c(_83,{el:1,id:1}); -_84=function(_88,arr){ -var te=d.byId(_83.id,(_88.ownerDocument||_88)); -if(!te||!_86(te)){ -return; -} -if(9==_88.nodeType){ -return _20(te,arr); -}else{ -if(_7e(te,_88)){ -return _20(te,arr); -} -} -}; -}else{ -if(ecs&&/\{\s*\[native code\]\s*\}/.test(String(ecs))&&_83.classes.length&&!_5){ -_86=_5c(_83,{el:1,classes:1,id:1}); -var _89=_83.classes.join(" "); -_84=function(_8a,arr,bag){ -var ret=_20(0,arr),te,x=0; -var _8b=_8a.getElementsByClassName(_89); -while((te=_8b[x++])){ -if(_86(te,_8a)&&_76(te,bag)){ -ret.push(te); -} -} -return ret; -}; -}else{ -if(!_87&&!_83.loops){ -_84=function(_8c,arr,bag){ -var ret=_20(0,arr),te,x=0; -var _8d=_8c.getElementsByTagName(_83.getTag()); -while((te=_8d[x++])){ -if(_76(te,bag)){ -ret.push(te); -} -} -return ret; -}; -}else{ -_86=_5c(_83,{el:1,tag:1,id:1}); -_84=function(_8e,arr,bag){ -var ret=_20(0,arr),te,x=0; -var _8f=_8e.getElementsByTagName(_83.getTag()); -while((te=_8f[x++])){ -if(_86(te,_8e)&&_76(te,bag)){ -ret.push(te); -} -} -return ret; -}; -} -} -} -}else{ -var _90={el:1}; -if(_87){ -_90.tag=1; -} -_86=_5c(_83,_90); -if("+"==_85){ -_84=_73(_86); -}else{ -if("~"==_85){ -_84=_77(_86); -}else{ -if(">"==_85){ -_84=_7a(_86); -} -} -} -} -return _81[_83.query]=_84; -}; -var _91=function(_92,_93){ -var _94=_20(_92),qp,x,te,qpl=_93.length,bag,ret; -for(var i=0;i0){ -bag={}; -ret.nozip=true; -} -var gef=_82(qp); -for(var j=0;(te=_94[j]);j++){ -gef(te,ret,bag); -} -if(!ret.length){ -break; -} -_94=ret; -} -return ret; -}; -var _95={},_96={}; -var _97=function(_98){ -var _99=_a(_1(_98)); -if(_99.length==1){ -var tef=_82(_99[0]); -return function(_9a){ -var r=tef(_9a,new _3()); -if(r){ -r.nozip=true; -} -return r; -}; -} -return function(_9b){ -return _91(_9b,_99); -}; -}; -var nua=navigator.userAgent; -var wk="WebKit/"; -var _9c=(d.isWebKit&&(nua.indexOf(wk)>0)&&(parseFloat(nua.split(wk)[1])>528)); -var _9d=d.isIE?"commentStrip":"nozip"; -var qsa="querySelectorAll"; -var _9e=(!!_4()[qsa]&&(!d.isSafari||(d.isSafari>3.1)||_9c)); -var _9f=/n\+\d|([^ ])?([>~+])([^ =])?/g; -var _a0=function(_a1,pre,ch,_a2){ -return ch?(pre?pre+" ":"")+ch+(_a2?" "+_a2:""):_a1; -}; -var _a3=function(_a4,_a5){ -_a4=_a4.replace(_9f,_a0); -if(_9e){ -var _a6=_96[_a4]; -if(_a6&&!_a5){ -return _a6; -} -} -var _a7=_95[_a4]; -if(_a7){ -return _a7; -} -var qcz=_a4.charAt(0); -var _a8=(-1==_a4.indexOf(" ")); -if((_a4.indexOf("#")>=0)&&(_a8)){ -_a5=true; -} -var _a9=(_9e&&(!_a5)&&(_7.indexOf(qcz)==-1)&&(!d.isIE||(_a4.indexOf(":")==-1))&&(!(_5&&(_a4.indexOf(".")>=0)))&&(_a4.indexOf(":contains")==-1)&&(_a4.indexOf(":checked")==-1)&&(_a4.indexOf("|=")==-1)); -if(_a9){ -var tq=(_7.indexOf(_a4.charAt(_a4.length-1))>=0)?(_a4+" *"):_a4; -return _96[_a4]=function(_aa){ -try{ -if(!((9==_aa.nodeType)||_a8)){ -throw ""; -} -var r=_aa[qsa](tq); -r[_9d]=true; -return r; -} -catch(e){ -return _a3(_a4,true)(_aa); -} -}; -}else{ -var _ab=_a4.split(/\s*,\s*/); -return _95[_a4]=((_ab.length<2)?_97(_a4):function(_ac){ -var _ad=0,ret=[],tp; -while((tp=_ab[_ad++])){ -ret=ret.concat(_97(tp)(_ac)); -} -return ret; -}); -} -}; -var _ae=0; -var _af=d.isIE?function(_b0){ -if(_8){ -return (_b0.getAttribute("_uid")||_b0.setAttribute("_uid",++_ae)||_ae); -}else{ -return _b0.uniqueID; -} -}:function(_b1){ -return (_b1._uid||(_b1._uid=++_ae)); -}; -var _76=function(_b2,bag){ -if(!bag){ -return 1; -} -var id=_af(_b2); -if(!bag[id]){ -return bag[id]=1; -} -return 0; -}; -var _b3="_zipIdx"; -var _b4=function(arr){ -if(arr&&arr.nozip){ -return (_3._wrap)?_3._wrap(arr):arr; -} -var ret=new _3(); -if(!arr||!arr.length){ -return ret; -} -if(arr[0]){ -ret.push(arr[0]); -} -if(arr.length<2){ -return ret; -} -_ae++; -if(d.isIE&&_8){ -var _b5=_ae+""; -arr[0].setAttribute(_b3,_b5); -for(var x=1,te;te=arr[x];x++){ -if(arr[x].getAttribute(_b3)!=_b5){ -ret.push(te); -} -te.setAttribute(_b3,_b5); -} -}else{ -if(d.isIE&&arr.commentStrip){ -try{ -for(var x=1,te;te=arr[x];x++){ -if(_21(te)){ -ret.push(te); -} -} -} -catch(e){ -} -}else{ -if(arr[0]){ -arr[0][_b3]=_ae; -} -for(var x=1,te;te=arr[x];x++){ -if(arr[x][_b3]!=_ae){ -ret.push(te); -} -te[_b3]=_ae; -} -} -} -return ret; -}; -d.query=function(_b6,_b7){ -_3=d._NodeListCtor; -if(!_b6){ -return new _3(); -} -if(_b6.constructor==_3){ -return _b6; -} -if(typeof _b6!="string"){ -return new _3(_b6); -} -if(typeof _b7=="string"){ -_b7=d.byId(_b7); -if(!_b7){ -return new _3(); -} -} -_b7=_b7||_4(); -var od=_b7.ownerDocument||_b7.documentElement; -_8=(_b7.contentType&&_b7.contentType=="application/xml")||(d.isOpera&&(_b7.doctype||od.toString()=="[object XMLDocument]"))||(!!od)&&(d.isIE?od.xml:(_b7.xmlVersion||od.xmlVersion)); -var r=_a3(_b6)(_b7); -if(r&&r.nozip&&!_3._wrap){ -return r; -} -return _b4(r); -}; -d.query.pseudos=_4c; -d._filterQueryResult=function(_b8,_b9){ -var _ba=new d._NodeListCtor(); -var _bb=_5c(_a(_b9)[0]); -for(var x=0,te;te=_b8[x];x++){ -if(_bb(te)){ -_ba.push(te); -} +if(!dojo._hasResource["dojo._base.query"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.query"] = true; +if(typeof dojo != "undefined"){ + dojo.provide("dojo._base.query"); + dojo.require("dojo._base.NodeList"); + dojo.require("dojo._base.lang"); + } -return _ba; -}; + +/* + dojo.query() architectural overview: + + dojo.query is a relatively full-featured CSS3 query library. It is + designed to take any valid CSS3 selector and return the nodes matching + the selector. To do this quickly, it processes queries in several + steps, applying caching where profitable. + + The steps (roughly in reverse order of the way they appear in the code): + 1.) check to see if we already have a "query dispatcher" + - if so, use that with the given parameterization. Skip to step 4. + 2.) attempt to determine which branch to dispatch the query to: + - JS (optimized DOM iteration) + - native (FF3.1+, Safari 3.1+, IE 8+) + 3.) tokenize and convert to executable "query dispatcher" + - this is where the lion's share of the complexity in the + system lies. In the DOM version, the query dispatcher is + assembled as a chain of "yes/no" test functions pertaining to + a section of a simple query statement (".blah:nth-child(odd)" + but not "div div", which is 2 simple statements). Individual + statement dispatchers are cached (to prevent re-definition) + as are entire dispatch chains (to make re-execution of the + same query fast) + 4.) the resulting query dispatcher is called in the passed scope + (by default the top-level document) + - for DOM queries, this results in a recursive, top-down + evaluation of nodes based on each simple query section + - for native implementations, this may mean working around spec + bugs. So be it. + 5.) matched nodes are pruned to ensure they are unique (if necessary) +*/ + +;(function(d){ + // define everything in a closure for compressability reasons. "d" is an + // alias to "dojo" (or the toolkit alias object, e.g., "acme"). + + //////////////////////////////////////////////////////////////////////// + // Toolkit aliases + //////////////////////////////////////////////////////////////////////// + + // if you are extracing dojo.query for use in your own system, you will + // need to provide these methods and properties. No other porting should be + // necessary, save for configuring the system to use a class other than + // dojo.NodeList as the return instance instantiator + var trim = d.trim; + var each = d.forEach; + // d.isIE; // float + // d.isSafari; // float + // d.isOpera; // float + // d.isWebKit; // float + // d.doc ; // document element + var qlc = d._NodeListCtor = d.NodeList; + + var getDoc = function(){ return d.doc; }; + // NOTE(alex): the spec is idiotic. CSS queries should ALWAYS be case-sensitive, but nooooooo + var cssCaseBug = ((d.isWebKit||d.isMozilla) && ((getDoc().compatMode) == "BackCompat")); + + //////////////////////////////////////////////////////////////////////// + // Global utilities + //////////////////////////////////////////////////////////////////////// + + + // on browsers that support the "children" collection we can avoid a lot of + // iteration on chaff (non-element) nodes. + // why. + var childNodesName = !!getDoc().firstChild["children"] ? "children" : "childNodes"; + + var specials = ">~+"; + + // global thunk to determine whether we should treat the current query as + // case sensitive or not. This switch is flipped by the query evaluator + // based on the document passed as the context to search. + var caseSensitive = false; + + // how high? + var yesman = function(){ return true; }; + + //////////////////////////////////////////////////////////////////////// + // Tokenizer + //////////////////////////////////////////////////////////////////////// + + var getQueryParts = function(query){ + // summary: + // state machine for query tokenization + // description: + // instead of using a brittle and slow regex-based CSS parser, + // dojo.query implements an AST-style query representation. This + // representation is only generated once per query. For example, + // the same query run multiple times or under different root nodes + // does not re-parse the selector expression but instead uses the + // cached data structure. The state machine implemented here + // terminates on the last " " (space) charachter and returns an + // ordered array of query component structures (or "parts"). Each + // part represents an operator or a simple CSS filtering + // expression. The structure for parts is documented in the code + // below. + + + // NOTE: + // this code is designed to run fast and compress well. Sacrifices + // to readibility and maintainability have been made. Your best + // bet when hacking the tokenizer is to put The Donnas on *really* + // loud (may we recommend their "Spend The Night" release?) and + // just assume you're gonna make mistakes. Keep the unit tests + // open and run them frequently. Knowing is half the battle ;-) + if(specials.indexOf(query.slice(-1)) >= 0){ + // if we end with a ">", "+", or "~", that means we're implicitly + // searching all children, so make it explicit + query += " * " + }else{ + // if you have not provided a terminator, one will be provided for + // you... + query += " "; + } + + var ts = function(/*Integer*/ s, /*Integer*/ e){ + // trim and slice. + + // take an index to start a string slice from and an end position + // and return a trimmed copy of that sub-string + return trim(query.slice(s, e)); + } + + // the overall data graph of the full query, as represented by queryPart objects + var queryParts = []; + + + // state keeping vars + var inBrackets = -1, inParens = -1, inMatchFor = -1, + inPseudo = -1, inClass = -1, inId = -1, inTag = -1, + lc = "", cc = "", pStart; + + // iteration vars + var x = 0, // index in the query + ql = query.length, + currentPart = null, // data structure representing the entire clause + _cp = null; // the current pseudo or attr matcher + + // several temporary variables are assigned to this structure durring a + // potential sub-expression match: + // attr: + // a string representing the current full attribute match in a + // bracket expression + // type: + // if there's an operator in a bracket expression, this is + // used to keep track of it + // value: + // the internals of parenthetical expression for a pseudo. for + // :nth-child(2n+1), value might be "2n+1" + + var endTag = function(){ + // called when the tokenizer hits the end of a particular tag name. + // Re-sets state variables for tag matching and sets up the matcher + // to handle the next type of token (tag or operator). + if(inTag >= 0){ + var tv = (inTag == x) ? null : ts(inTag, x); // .toLowerCase(); + currentPart[ (specials.indexOf(tv) < 0) ? "tag" : "oper" ] = tv; + inTag = -1; + } + } + + var endId = function(){ + // called when the tokenizer might be at the end of an ID portion of a match + if(inId >= 0){ + currentPart.id = ts(inId, x).replace(/\\/g, ""); + inId = -1; + } + } + + var endClass = function(){ + // called when the tokenizer might be at the end of a class name + // match. CSS allows for multiple classes, so we augment the + // current item with another class in its list + if(inClass >= 0){ + currentPart.classes.push(ts(inClass+1, x).replace(/\\/g, "")); + inClass = -1; + } + } + + var endAll = function(){ + // at the end of a simple fragment, so wall off the matches + endId(); endTag(); endClass(); + } + + var endPart = function(){ + endAll(); + if(inPseudo >= 0){ + currentPart.pseudos.push({ name: ts(inPseudo+1, x) }); + } + // hint to the selector engine to tell it whether or not it + // needs to do any iteration. Many simple selectors don't, and + // we can avoid significant construction-time work by advising + // the system to skip them + currentPart.loops = ( + currentPart.pseudos.length || + currentPart.attrs.length || + currentPart.classes.length ); + + currentPart.oquery = currentPart.query = ts(pStart, x); // save the full expression as a string + + + // otag/tag are hints to suggest to the system whether or not + // it's an operator or a tag. We save a copy of otag since the + // tag name is cast to upper-case in regular HTML matches. The + // system has a global switch to figure out if the current + // expression needs to be case sensitive or not and it will use + // otag or tag accordingly + currentPart.otag = currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*"); + + if(currentPart.tag){ + // if we're in a case-insensitive HTML doc, we likely want + // the toUpperCase when matching on element.tagName. If we + // do it here, we can skip the string op per node + // comparison + currentPart.tag = currentPart.tag.toUpperCase(); + } + + // add the part to the list + if(queryParts.length && (queryParts[queryParts.length-1].oper)){ + // operators are always infix, so we remove them from the + // list and attach them to the next match. The evaluator is + // responsible for sorting out how to handle them. + currentPart.infixOper = queryParts.pop(); + currentPart.query = currentPart.infixOper.query + " " + currentPart.query; + /* + console.debug( "swapping out the infix", + currentPart.infixOper, + "and attaching it to", + currentPart); + */ + } + queryParts.push(currentPart); + + currentPart = null; + } + + // iterate over the query, charachter by charachter, building up a + // list of query part objects + for(; lc=cc, cc=query.charAt(x), x < ql; x++){ + // cc: the current character in the match + // lc: the last charachter (if any) + + // someone is trying to escape something, so don't try to match any + // fragments. We assume we're inside a literal. + if(lc == "\\"){ continue; } + if(!currentPart){ // a part was just ended or none has yet been created + // NOTE: I hate all this alloc, but it's shorter than writing tons of if's + pStart = x; + // rules describe full CSS sub-expressions, like: + // #someId + // .className:first-child + // but not: + // thinger > div.howdy[type=thinger] + // the indidual components of the previous query would be + // split into 3 parts that would be represented a structure + // like: + // [ + // { + // query: "thinger", + // tag: "thinger", + // }, + // { + // query: "div.howdy[type=thinger]", + // classes: ["howdy"], + // infixOper: { + // query: ">", + // oper: ">", + // } + // }, + // ] + currentPart = { + query: null, // the full text of the part's rule + pseudos: [], // CSS supports multiple pseud-class matches in a single rule + attrs: [], // CSS supports multi-attribute match, so we need an array + classes: [], // class matches may be additive, e.g.: .thinger.blah.howdy + tag: null, // only one tag... + oper: null, // ...or operator per component. Note that these wind up being exclusive. + id: null, // the id component of a rule + getTag: function(){ + return (caseSensitive) ? this.otag : this.tag; + } + }; + + // if we don't have a part, we assume we're going to start at + // the beginning of a match, which should be a tag name. This + // might fault a little later on, but we detect that and this + // iteration will still be fine. + inTag = x; + } + + if(inBrackets >= 0){ + // look for a the close first + if(cc == "]"){ // if we're in a [...] clause and we end, do assignment + if(!_cp.attr){ + // no attribute match was previously begun, so we + // assume this is an attribute existance match in the + // form of [someAttributeName] + _cp.attr = ts(inBrackets+1, x); + }else{ + // we had an attribute already, so we know that we're + // matching some sort of value, as in [attrName=howdy] + _cp.matchFor = ts((inMatchFor||inBrackets+1), x); + } + var cmf = _cp.matchFor; + if(cmf){ + // try to strip quotes from the matchFor value. We want + // [attrName=howdy] to match the same + // as [attrName = 'howdy' ] + if( (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){ + _cp.matchFor = cmf.slice(1, -1); + } + } + // end the attribute by adding it to the list of attributes. + currentPart.attrs.push(_cp); + _cp = null; // necessary? + inBrackets = inMatchFor = -1; + }else if(cc == "="){ + // if the last char was an operator prefix, make sure we + // record it along with the "=" operator. + var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : ""; + _cp.type = addToCc+cc; + _cp.attr = ts(inBrackets+1, x-addToCc.length); + inMatchFor = x+1; + } + // now look for other clause parts + }else if(inParens >= 0){ + // if we're in a parenthetical expression, we need to figure + // out if it's attached to a pseduo-selector rule like + // :nth-child(1) + if(cc == ")"){ + if(inPseudo >= 0){ + _cp.value = ts(inParens+1, x); + } + inPseudo = inParens = -1; + } + }else if(cc == "#"){ + // start of an ID match + endAll(); + inId = x+1; + }else if(cc == "."){ + // start of a class match + endAll(); + inClass = x; + }else if(cc == ":"){ + // start of a pseudo-selector match + endAll(); + inPseudo = x; + }else if(cc == "["){ + // start of an attribute match. + endAll(); + inBrackets = x; + // provide a new structure for the attribute match to fill-in + _cp = { + /*===== + attr: null, type: null, matchFor: null + =====*/ + }; + }else if(cc == "("){ + // we really only care if we've entered a parenthetical + // expression if we're already inside a pseudo-selector match + if(inPseudo >= 0){ + // provide a new structure for the pseudo match to fill-in + _cp = { + name: ts(inPseudo+1, x), + value: null + } + currentPart.pseudos.push(_cp); + } + inParens = x; + }else if( + (cc == " ") && + // if it's a space char and the last char is too, consume the + // current one without doing more work + (lc != cc) + ){ + endPart(); + } + } + return queryParts; + }; + + + //////////////////////////////////////////////////////////////////////// + // DOM query infrastructure + //////////////////////////////////////////////////////////////////////// + + var agree = function(first, second){ + // the basic building block of the yes/no chaining system. agree(f1, + // f2) generates a new function which returns the boolean results of + // both of the passed functions to a single logical-anded result. If + // either are not possed, the other is used exclusively. + if(!first){ return second; } + if(!second){ return first; } + + return function(){ + return first.apply(window, arguments) && second.apply(window, arguments); + } + }; + + var getArr = function(i, arr){ + // helps us avoid array alloc when we don't need it + var r = arr||[]; // FIXME: should this be 'new d._NodeListCtor()' ? + if(i){ r.push(i); } + return r; + }; + + var _isElement = function(n){ return (1 == n.nodeType); }; + + // FIXME: need to coalesce _getAttr with defaultGetter + var blank = ""; + var _getAttr = function(elem, attr){ + if(!elem){ return blank; } + if(attr == "class"){ + return elem.className || blank; + } + if(attr == "for"){ + return elem.htmlFor || blank; + } + if(attr == "style"){ + return elem.style.cssText || blank; + } + return (caseSensitive ? elem.getAttribute(attr) : elem.getAttribute(attr, 2)) || blank; + }; + + var attrs = { + "*=": function(attr, value){ + return function(elem){ + // E[foo*="bar"] + // an E element whose "foo" attribute value contains + // the substring "bar" + return (_getAttr(elem, attr).indexOf(value)>=0); + } + }, + "^=": function(attr, value){ + // E[foo^="bar"] + // an E element whose "foo" attribute value begins exactly + // with the string "bar" + return function(elem){ + return (_getAttr(elem, attr).indexOf(value)==0); + } + }, + "$=": function(attr, value){ + // E[foo$="bar"] + // an E element whose "foo" attribute value ends exactly + // with the string "bar" + var tval = " "+value; + return function(elem){ + var ea = " "+_getAttr(elem, attr); + return (ea.lastIndexOf(value)==(ea.length-value.length)); + } + }, + "~=": function(attr, value){ + // E[foo~="bar"] + // an E element whose "foo" attribute value is a list of + // space-separated values, one of which is exactly equal + // to "bar" + + // return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]"; + var tval = " "+value+" "; + return function(elem){ + var ea = " "+_getAttr(elem, attr)+" "; + return (ea.indexOf(tval)>=0); + } + }, + "|=": function(attr, value){ + // E[hreflang|="en"] + // an E element whose "hreflang" attribute has a + // hyphen-separated list of values beginning (from the + // left) with "en" + var valueDash = " "+value+"-"; + return function(elem){ + var ea = " "+_getAttr(elem, attr); + return ( + (ea == value) || + (ea.indexOf(valueDash)==0) + ); + } + }, + "=": function(attr, value){ + return function(elem){ + return (_getAttr(elem, attr) == value); + } + } + }; + + // avoid testing for node type if we can. Defining this in the negative + // here to avoid negation in the fast path. + var _noNES = (typeof getDoc().firstChild.nextElementSibling == "undefined"); + var _ns = !_noNES ? "nextElementSibling" : "nextSibling"; + var _ps = !_noNES ? "previousElementSibling" : "previousSibling"; + var _simpleNodeTest = (_noNES ? _isElement : yesman); + + var _lookLeft = function(node){ + // look left + while(node = node[_ps]){ + if(_simpleNodeTest(node)){ return false; } + } + return true; + }; + + var _lookRight = function(node){ + // look right + while(node = node[_ns]){ + if(_simpleNodeTest(node)){ return false; } + } + return true; + }; + + var getNodeIndex = function(node){ + var root = node.parentNode; + var i = 0, + tret = root[childNodesName], + ci = (node["_i"]||-1), + cl = (root["_l"]||-1); + + if(!tret){ return -1; } + var l = tret.length; + + // we calcuate the parent length as a cheap way to invalidate the + // cache. It's not 100% accurate, but it's much more honest than what + // other libraries do + if( cl == l && ci >= 0 && cl >= 0 ){ + // if it's legit, tag and release + return ci; + } + + // else re-key things + root["_l"] = l; + ci = -1; + for(var te = root["firstElementChild"]||root["firstChild"]; te; te = te[_ns]){ + if(_simpleNodeTest(te)){ + te["_i"] = ++i; + if(node === te){ + // NOTE: + // shortcuting the return at this step in indexing works + // very well for benchmarking but we avoid it here since + // it leads to potential O(n^2) behavior in sequential + // getNodexIndex operations on a previously un-indexed + // parent. We may revisit this at a later time, but for + // now we just want to get the right answer more often + // than not. + ci = i; + } + } + } + return ci; + }; + + var isEven = function(elem){ + return !((getNodeIndex(elem)) % 2); + }; + + var isOdd = function(elem){ + return ((getNodeIndex(elem)) % 2); + }; + + var pseudos = { + "checked": function(name, condition){ + return function(elem){ + return !!("checked" in elem ? elem.checked : elem.selected); + } + }, + "first-child": function(){ return _lookLeft; }, + "last-child": function(){ return _lookRight; }, + "only-child": function(name, condition){ + return function(node){ + if(!_lookLeft(node)){ return false; } + if(!_lookRight(node)){ return false; } + return true; + }; + }, + "empty": function(name, condition){ + return function(elem){ + // DomQuery and jQuery get this wrong, oddly enough. + // The CSS 3 selectors spec is pretty explicit about it, too. + var cn = elem.childNodes; + var cnl = elem.childNodes.length; + // if(!cnl){ return true; } + for(var x=cnl-1; x >= 0; x--){ + var nt = cn[x].nodeType; + if((nt === 1)||(nt == 3)){ return false; } + } + return true; + } + }, + "contains": function(name, condition){ + var cz = condition.charAt(0); + if( cz == '"' || cz == "'" ){ //remove quote + condition = condition.slice(1, -1); + } + return function(elem){ + return (elem.innerHTML.indexOf(condition) >= 0); + } + }, + "not": function(name, condition){ + var p = getQueryParts(condition)[0]; + var ignores = { el: 1 }; + if(p.tag != "*"){ + ignores.tag = 1; + } + if(!p.classes.length){ + ignores.classes = 1; + } + var ntf = getSimpleFilterFunc(p, ignores); + return function(elem){ + return (!ntf(elem)); + } + }, + "nth-child": function(name, condition){ + var pi = parseInt; + // avoid re-defining function objects if we can + if(condition == "odd"){ + return isOdd; + }else if(condition == "even"){ + return isEven; + } + // FIXME: can we shorten this? + if(condition.indexOf("n") != -1){ + var tparts = condition.split("n", 2); + var pred = tparts[0] ? ((tparts[0] == '-') ? -1 : pi(tparts[0])) : 1; + var idx = tparts[1] ? pi(tparts[1]) : 0; + var lb = 0, ub = -1; + if(pred > 0){ + if(idx < 0){ + idx = (idx % pred) && (pred + (idx % pred)); + }else if(idx>0){ + if(idx >= pred){ + lb = idx - idx % pred; + } + idx = idx % pred; + } + }else if(pred<0){ + pred *= -1; + // idx has to be greater than 0 when pred is negative; + // shall we throw an error here? + if(idx > 0){ + ub = idx; + idx = idx % pred; + } + } + if(pred > 0){ + return function(elem){ + var i = getNodeIndex(elem); + return (i>=lb) && (ub<0 || i<=ub) && ((i % pred) == idx); + } + }else{ + condition = idx; + } + } + var ncount = pi(condition); + return function(elem){ + return (getNodeIndex(elem) == ncount); + } + } + }; + + var defaultGetter = (d.isIE) ? function(cond){ + var clc = cond.toLowerCase(); + if(clc == "class"){ cond = "className"; } + return function(elem){ + return (caseSensitive ? elem.getAttribute(cond) : elem[cond]||elem[clc]); + } + } : function(cond){ + return function(elem){ + return (elem && elem.getAttribute && elem.hasAttribute(cond)); + } + }; + + var getSimpleFilterFunc = function(query, ignores){ + // generates a node tester function based on the passed query part. The + // query part is one of the structures generatd by the query parser + // when it creates the query AST. The "ignores" object specifies which + // (if any) tests to skip, allowing the system to avoid duplicating + // work where it may have already been taken into account by other + // factors such as how the nodes to test were fetched in the first + // place + if(!query){ return yesman; } + ignores = ignores||{}; + + var ff = null; + + if(!("el" in ignores)){ + ff = agree(ff, _isElement); + } + + if(!("tag" in ignores)){ + if(query.tag != "*"){ + ff = agree(ff, function(elem){ + return (elem && (elem.tagName == query.getTag())); + }); + } + } + + if(!("classes" in ignores)){ + each(query.classes, function(cname, idx, arr){ + // get the class name + /* + var isWildcard = cname.charAt(cname.length-1) == "*"; + if(isWildcard){ + cname = cname.substr(0, cname.length-1); + } + // I dislike the regex thing, even if memozied in a cache, but it's VERY short + var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)"); + */ + var re = new RegExp("(?:^|\\s)" + cname + "(?:\\s|$)"); + ff = agree(ff, function(elem){ + return re.test(elem.className); + }); + ff.count = idx; + }); + } + + if(!("pseudos" in ignores)){ + each(query.pseudos, function(pseudo){ + var pn = pseudo.name; + if(pseudos[pn]){ + ff = agree(ff, pseudos[pn](pn, pseudo.value)); + } + }); + } + + if(!("attrs" in ignores)){ + each(query.attrs, function(attr){ + var matcher; + var a = attr.attr; + // type, attr, matchFor + if(attr.type && attrs[attr.type]){ + matcher = attrs[attr.type](a, attr.matchFor); + }else if(a.length){ + matcher = defaultGetter(a); + } + if(matcher){ + ff = agree(ff, matcher); + } + }); + } + + if(!("id" in ignores)){ + if(query.id){ + ff = agree(ff, function(elem){ + return (!!elem && (elem.id == query.id)); + }); + } + } + + if(!ff){ + if(!("default" in ignores)){ + ff = yesman; + } + } + return ff; + }; + + var _nextSibling = function(filterFunc){ + return function(node, ret, bag){ + while(node = node[_ns]){ + if(_noNES && (!_isElement(node))){ continue; } + if( + (!bag || _isUnique(node, bag)) && + filterFunc(node) + ){ + ret.push(node); + } + break; + } + return ret; + } + }; + + var _nextSiblings = function(filterFunc){ + return function(root, ret, bag){ + var te = root[_ns]; + while(te){ + if(_simpleNodeTest(te)){ + if(bag && !_isUnique(te, bag)){ + break; + } + if(filterFunc(te)){ + ret.push(te); + } + } + te = te[_ns]; + } + return ret; + } + }; + + // get an array of child *elements*, skipping text and comment nodes + var _childElements = function(filterFunc){ + filterFunc = filterFunc||yesman; + return function(root, ret, bag){ + // get an array of child elements, skipping text and comment nodes + var te, x = 0, tret = root[childNodesName]; + while(te = tret[x++]){ + if( + _simpleNodeTest(te) && + (!bag || _isUnique(te, bag)) && + (filterFunc(te, x)) + ){ + ret.push(te); + } + } + return ret; + }; + }; + + /* + // thanks, Dean! + var itemIsAfterRoot = d.isIE ? function(item, root){ + return (item.sourceIndex > root.sourceIndex); + } : function(item, root){ + return (item.compareDocumentPosition(root) == 2); + }; + */ + + // test to see if node is below root + var _isDescendant = function(node, root){ + var pn = node.parentNode; + while(pn){ + if(pn == root){ + break; + } + pn = pn.parentNode; + } + return !!pn; + }; + + var _getElementsFuncCache = {}; + + var getElementsFunc = function(query){ + var retFunc = _getElementsFuncCache[query.query]; + // if we've got a cached dispatcher, just use that + if(retFunc){ return retFunc; } + // else, generate a new on + + // NOTE: + // this function returns a function that searches for nodes and + // filters them. The search may be specialized by infix operators + // (">", "~", or "+") else it will default to searching all + // descendants (the " " selector). Once a group of children is + // founde, a test function is applied to weed out the ones we + // don't want. Many common cases can be fast-pathed. We spend a + // lot of cycles to create a dispatcher that doesn't do more work + // than necessary at any point since, unlike this function, the + // dispatchers will be called every time. The logic of generating + // efficient dispatchers looks like this in pseudo code: + // + // # if it's a purely descendant query (no ">", "+", or "~" modifiers) + // if infixOperator == " ": + // if only(id): + // return def(root): + // return d.byId(id, root); + // + // elif id: + // return def(root): + // return filter(d.byId(id, root)); + // + // elif cssClass && getElementsByClassName: + // return def(root): + // return filter(root.getElementsByClassName(cssClass)); + // + // elif only(tag): + // return def(root): + // return root.getElementsByTagName(tagName); + // + // else: + // # search by tag name, then filter + // return def(root): + // return filter(root.getElementsByTagName(tagName||"*")); + // + // elif infixOperator == ">": + // # search direct children + // return def(root): + // return filter(root.children); + // + // elif infixOperator == "+": + // # search next sibling + // return def(root): + // return filter(root.nextElementSibling); + // + // elif infixOperator == "~": + // # search rightward siblings + // return def(root): + // return filter(nextSiblings(root)); + + var io = query.infixOper; + var oper = (io ? io.oper : ""); + // the default filter func which tests for all conditions in the query + // part. This is potentially inefficient, so some optimized paths may + // re-define it to test fewer things. + var filterFunc = getSimpleFilterFunc(query, { el: 1 }); + var qt = query.tag; + var wildcardTag = ("*" == qt); + var ecs = getDoc()["getElementsByClassName"]; + + if(!oper){ + // if there's no infix operator, then it's a descendant query. ID + // and "elements by class name" variants can be accelerated so we + // call them out explicitly: + if(query.id){ + // testing shows that the overhead of yesman() is acceptable + // and can save us some bytes vs. re-defining the function + // everywhere. + filterFunc = (!query.loops && wildcardTag) ? + yesman : + getSimpleFilterFunc(query, { el: 1, id: 1 }); + + retFunc = function(root, arr){ + var te = d.byId(query.id, (root.ownerDocument||root)); + if(!te || !filterFunc(te)){ return; } + if(9 == root.nodeType){ // if root's a doc, we just return directly + return getArr(te, arr); + }else{ // otherwise check ancestry + if(_isDescendant(te, root)){ + return getArr(te, arr); + } + } + } + }else if( + ecs && + // isAlien check. Workaround for Prototype.js being totally evil/dumb. + /\{\s*\[native code\]\s*\}/.test(String(ecs)) && + query.classes.length && + !cssCaseBug + ){ + // it's a class-based query and we've got a fast way to run it. + + // ignore class and ID filters since we will have handled both + filterFunc = getSimpleFilterFunc(query, { el: 1, classes: 1, id: 1 }); + var classesString = query.classes.join(" "); + retFunc = function(root, arr, bag){ + var ret = getArr(0, arr), te, x=0; + var tret = root.getElementsByClassName(classesString); + while((te = tret[x++])){ + if(filterFunc(te, root) && _isUnique(te, bag)){ + ret.push(te); + } + } + return ret; + }; + + }else if(!wildcardTag && !query.loops){ + // it's tag only. Fast-path it. + retFunc = function(root, arr, bag){ + var ret = getArr(0, arr), te, x=0; + var tret = root.getElementsByTagName(query.getTag()); + while((te = tret[x++])){ + if(_isUnique(te, bag)){ + ret.push(te); + } + } + return ret; + }; + }else{ + // the common case: + // a descendant selector without a fast path. By now it's got + // to have a tag selector, even if it's just "*" so we query + // by that and filter + filterFunc = getSimpleFilterFunc(query, { el: 1, tag: 1, id: 1 }); + retFunc = function(root, arr, bag){ + var ret = getArr(0, arr), te, x=0; + // we use getTag() to avoid case sensitivity issues + var tret = root.getElementsByTagName(query.getTag()); + while((te = tret[x++])){ + if(filterFunc(te, root) && _isUnique(te, bag)){ + ret.push(te); + } + } + return ret; + }; + } + }else{ + // the query is scoped in some way. Instead of querying by tag we + // use some other collection to find candidate nodes + var skipFilters = { el: 1 }; + if(wildcardTag){ + skipFilters.tag = 1; + } + filterFunc = getSimpleFilterFunc(query, skipFilters); + if("+" == oper){ + retFunc = _nextSibling(filterFunc); + }else if("~" == oper){ + retFunc = _nextSiblings(filterFunc); + }else if(">" == oper){ + retFunc = _childElements(filterFunc); + } + } + // cache it and return + return _getElementsFuncCache[query.query] = retFunc; + }; + + var filterDown = function(root, queryParts){ + // NOTE: + // this is the guts of the DOM query system. It takes a list of + // parsed query parts and a root and finds children which match + // the selector represented by the parts + var candidates = getArr(root), qp, x, te, qpl = queryParts.length, bag, ret; + + for(var i = 0; i < qpl; i++){ + ret = []; + qp = queryParts[i]; + x = candidates.length - 1; + if(x > 0){ + // if we have more than one root at this level, provide a new + // hash to use for checking group membership but tell the + // system not to post-filter us since we will already have been + // gauranteed to be unique + bag = {}; + ret.nozip = true; + } + var gef = getElementsFunc(qp); + for(var j = 0; (te = candidates[j]); j++){ + // for every root, get the elements that match the descendant + // selector, adding them to the "ret" array and filtering them + // via membership in this level's bag. If there are more query + // parts, then this level's return will be used as the next + // level's candidates + gef(te, ret, bag); + } + if(!ret.length){ break; } + candidates = ret; + } + return ret; + }; + + //////////////////////////////////////////////////////////////////////// + // the query runner + //////////////////////////////////////////////////////////////////////// + + // these are the primary caches for full-query results. The query + // dispatcher functions are generated then stored here for hash lookup in + // the future + var _queryFuncCacheDOM = {}, + _queryFuncCacheQSA = {}; + + // this is the second level of spliting, from full-length queries (e.g., + // "div.foo .bar") into simple query expressions (e.g., ["div.foo", + // ".bar"]) + var getStepQueryFunc = function(query){ + var qparts = getQueryParts(trim(query)); + + // if it's trivial, avoid iteration and zipping costs + if(qparts.length == 1){ + // we optimize this case here to prevent dispatch further down the + // chain, potentially slowing things down. We could more elegantly + // handle this in filterDown(), but it's slower for simple things + // that need to be fast (e.g., "#someId"). + var tef = getElementsFunc(qparts[0]); + return function(root){ + var r = tef(root, new qlc()); + if(r){ r.nozip = true; } + return r; + } + } + + // otherwise, break it up and return a runner that iterates over the parts recursively + return function(root){ + return filterDown(root, qparts); + } + }; + + // NOTES: + // * we can't trust QSA for anything but document-rooted queries, so + // caching is split into DOM query evaluators and QSA query evaluators + // * caching query results is dirty and leak-prone (or, at a minimum, + // prone to unbounded growth). Other toolkits may go this route, but + // they totally destroy their own ability to manage their memory + // footprint. If we implement it, it should only ever be with a fixed + // total element reference # limit and an LRU-style algorithm since JS + // has no weakref support. Caching compiled query evaluators is also + // potentially problematic, but even on large documents the size of the + // query evaluators is often < 100 function objects per evaluator (and + // LRU can be applied if it's ever shown to be an issue). + // * since IE's QSA support is currently only for HTML documents and even + // then only in IE 8's "standards mode", we have to detect our dispatch + // route at query time and keep 2 separate caches. Ugg. + + // we need to determine if we think we can run a given query via + // querySelectorAll or if we'll need to fall back on DOM queries to get + // there. We need a lot of information about the environment and the query + // to make the determiniation (e.g. does it support QSA, does the query in + // question work in the native QSA impl, etc.). + var nua = navigator.userAgent; + // some versions of Safari provided QSA, but it was buggy and crash-prone. + // We need te detect the right "internal" webkit version to make this work. + var wk = "WebKit/"; + var is525 = ( + d.isWebKit && + (nua.indexOf(wk) > 0) && + (parseFloat(nua.split(wk)[1]) > 528) + ); + + // IE QSA queries may incorrectly include comment nodes, so we throw the + // zipping function into "remove" comments mode instead of the normal "skip + // it" which every other QSA-clued browser enjoys + var noZip = d.isIE ? "commentStrip" : "nozip"; + + var qsa = "querySelectorAll"; + var qsaAvail = ( + !!getDoc()[qsa] && + // see #5832 + (!d.isSafari || (d.isSafari > 3.1) || is525 ) + ); + + //Don't bother with n+3 type of matches, IE complains if we modify those. + var infixSpaceRe = /n\+\d|([^ ])?([>~+])([^ =])?/g; + var infixSpaceFunc = function(match, pre, ch, post) { + return ch ? (pre ? pre + " " : "") + ch + (post ? " " + post : "") : /*n+3*/ match; + }; + + var getQueryFunc = function(query, forceDOM){ + //Normalize query. The CSS3 selectors spec allows for omitting spaces around + //infix operators, >, ~ and + + //Do the work here since detection for spaces is used as a simple "not use QSA" + //test below. + query = query.replace(infixSpaceRe, infixSpaceFunc); + + if(qsaAvail){ + // if we've got a cached variant and we think we can do it, run it! + var qsaCached = _queryFuncCacheQSA[query]; + if(qsaCached && !forceDOM){ return qsaCached; } + } + + // else if we've got a DOM cached variant, assume that we already know + // all we need to and use it + var domCached = _queryFuncCacheDOM[query]; + if(domCached){ return domCached; } + + // TODO: + // today we're caching DOM and QSA branches separately so we + // recalc useQSA every time. If we had a way to tag root+query + // efficiently, we'd be in good shape to do a global cache. + + var qcz = query.charAt(0); + var nospace = (-1 == query.indexOf(" ")); + + // byId searches are wicked fast compared to QSA, even when filtering + // is required + if( (query.indexOf("#") >= 0) && (nospace) ){ + forceDOM = true; + } + + var useQSA = ( + qsaAvail && (!forceDOM) && + // as per CSS 3, we can't currently start w/ combinator: + // http://www.w3.org/TR/css3-selectors/#w3cselgrammar + (specials.indexOf(qcz) == -1) && + // IE's QSA impl sucks on pseudos + (!d.isIE || (query.indexOf(":") == -1)) && + + (!(cssCaseBug && (query.indexOf(".") >= 0))) && + + // FIXME: + // need to tighten up browser rules on ":contains" and "|=" to + // figure out which aren't good + // Latest webkit (around 531.21.8) does not seem to do well with :checked on option + // elements, even though according to spec, selected options should + // match :checked. So go nonQSA for it: + // http://bugs.dojotoolkit.org/ticket/5179 + (query.indexOf(":contains") == -1) && (query.indexOf(":checked") == -1) && + (query.indexOf("|=") == -1) // some browsers don't grok it + ); + + // TODO: + // if we've got a descendant query (e.g., "> .thinger" instead of + // just ".thinger") in a QSA-able doc, but are passed a child as a + // root, it should be possible to give the item a synthetic ID and + // trivially rewrite the query to the form "#synid > .thinger" to + // use the QSA branch + + + if(useQSA){ + var tq = (specials.indexOf(query.charAt(query.length-1)) >= 0) ? + (query + " *") : query; + return _queryFuncCacheQSA[query] = function(root){ + try{ + // the QSA system contains an egregious spec bug which + // limits us, effectively, to only running QSA queries over + // entire documents. See: + // http://ejohn.org/blog/thoughts-on-queryselectorall/ + // despite this, we can also handle QSA runs on simple + // selectors, but we don't want detection to be expensive + // so we're just checking for the presence of a space char + // right now. Not elegant, but it's cheaper than running + // the query parser when we might not need to + if(!((9 == root.nodeType) || nospace)){ throw ""; } + var r = root[qsa](tq); + // skip expensive duplication checks and just wrap in a NodeList + r[noZip] = true; + return r; + }catch(e){ + // else run the DOM branch on this query, ensuring that we + // default that way in the future + return getQueryFunc(query, true)(root); + } + } + }else{ + // DOM branch + var parts = query.split(/\s*,\s*/); + return _queryFuncCacheDOM[query] = ((parts.length < 2) ? + // if not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher + getStepQueryFunc(query) : + // if it *is* a complex query, break it up into its + // constituent parts and return a dispatcher that will + // merge the parts when run + function(root){ + var pindex = 0, // avoid array alloc for every invocation + ret = [], + tp; + while((tp = parts[pindex++])){ + ret = ret.concat(getStepQueryFunc(tp)(root)); + } + return ret; + } + ); + } + }; + + var _zipIdx = 0; + + // NOTE: + // this function is Moo inspired, but our own impl to deal correctly + // with XML in IE + var _nodeUID = d.isIE ? function(node){ + if(caseSensitive){ + // XML docs don't have uniqueID on their nodes + return (node.getAttribute("_uid") || node.setAttribute("_uid", ++_zipIdx) || _zipIdx); + + }else{ + return node.uniqueID; + } + } : + function(node){ + return (node._uid || (node._uid = ++_zipIdx)); + }; + + // determine if a node in is unique in a "bag". In this case we don't want + // to flatten a list of unique items, but rather just tell if the item in + // question is already in the bag. Normally we'd just use hash lookup to do + // this for us but IE's DOM is busted so we can't really count on that. On + // the upside, it gives us a built in unique ID function. + var _isUnique = function(node, bag){ + if(!bag){ return 1; } + var id = _nodeUID(node); + if(!bag[id]){ return bag[id] = 1; } + return 0; + }; + + // attempt to efficiently determine if an item in a list is a dupe, + // returning a list of "uniques", hopefully in doucment order + var _zipIdxName = "_zipIdx"; + var _zip = function(arr){ + if(arr && arr.nozip){ + return (qlc._wrap) ? qlc._wrap(arr) : arr; + } + // var ret = new d._NodeListCtor(); + var ret = new qlc(); + if(!arr || !arr.length){ return ret; } + if(arr[0]){ + ret.push(arr[0]); + } + if(arr.length < 2){ return ret; } + + _zipIdx++; + + // we have to fork here for IE and XML docs because we can't set + // expandos on their nodes (apparently). *sigh* + if(d.isIE && caseSensitive){ + var szidx = _zipIdx+""; + arr[0].setAttribute(_zipIdxName, szidx); + for(var x = 1, te; te = arr[x]; x++){ + if(arr[x].getAttribute(_zipIdxName) != szidx){ + ret.push(te); + } + te.setAttribute(_zipIdxName, szidx); + } + }else if(d.isIE && arr.commentStrip){ + try{ + for(var x = 1, te; te = arr[x]; x++){ + if(_isElement(te)){ + ret.push(te); + } + } + }catch(e){ /* squelch */ } + }else{ + if(arr[0]){ arr[0][_zipIdxName] = _zipIdx; } + for(var x = 1, te; te = arr[x]; x++){ + if(arr[x][_zipIdxName] != _zipIdx){ + ret.push(te); + } + te[_zipIdxName] = _zipIdx; + } + } + return ret; + }; + + // the main executor + d.query = function(/*String*/ query, /*String|DOMNode?*/ root){ + // summary: + // Returns nodes which match the given CSS3 selector, searching the + // entire document by default but optionally taking a node to scope + // the search by. Returns an instance of dojo.NodeList. + // description: + // dojo.query() is the swiss army knife of DOM node manipulation in + // Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's + // "$" function, dojo.query provides robust, high-performance + // CSS-based node selector support with the option of scoping searches + // to a particular sub-tree of a document. + // + // Supported Selectors: + // -------------------- + // + // dojo.query() supports a rich set of CSS3 selectors, including: + // + // * class selectors (e.g., `.foo`) + // * node type selectors like `span` + // * ` ` descendant selectors + // * `>` child element selectors + // * `#foo` style ID selectors + // * `*` universal selector + // * `~`, the immediately preceeded-by sibling selector + // * `+`, the preceeded-by sibling selector + // * attribute queries: + // | * `[foo]` attribute presence selector + // | * `[foo='bar']` attribute value exact match + // | * `[foo~='bar']` attribute value list item match + // | * `[foo^='bar']` attribute start match + // | * `[foo$='bar']` attribute end match + // | * `[foo*='bar']` attribute substring match + // * `:first-child`, `:last-child`, and `:only-child` positional selectors + // * `:empty` content emtpy selector + // * `:checked` pseudo selector + // * `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations + // * `:nth-child(even)`, `:nth-child(odd)` positional selectors + // * `:not(...)` negation pseudo selectors + // + // Any legal combination of these selectors will work with + // `dojo.query()`, including compound selectors ("," delimited). + // Very complex and useful searches can be constructed with this + // palette of selectors and when combined with functions for + // manipulation presented by dojo.NodeList, many types of DOM + // manipulation operations become very straightforward. + // + // Unsupported Selectors: + // ---------------------- + // + // While dojo.query handles many CSS3 selectors, some fall outside of + // what's resaonable for a programmatic node querying engine to + // handle. Currently unsupported selectors include: + // + // * namespace-differentiated selectors of any form + // * all `::` pseduo-element selectors + // * certain pseduo-selectors which don't get a lot of day-to-day use: + // | * `:root`, `:lang()`, `:target`, `:focus` + // * all visual and state selectors: + // | * `:root`, `:active`, `:hover`, `:visisted`, `:link`, + // `:enabled`, `:disabled` + // * `:*-of-type` pseudo selectors + // + // dojo.query and XML Documents: + // ----------------------------- + // + // `dojo.query` (as of dojo 1.2) supports searching XML documents + // in a case-sensitive manner. If an HTML document is served with + // a doctype that forces case-sensitivity (e.g., XHTML 1.1 + // Strict), dojo.query() will detect this and "do the right + // thing". Case sensitivity is dependent upon the document being + // searched and not the query used. It is therefore possible to + // use case-sensitive queries on strict sub-documents (iframes, + // etc.) or XML documents while still assuming case-insensitivity + // for a host/root document. + // + // Non-selector Queries: + // --------------------- + // + // If something other than a String is passed for the query, + // `dojo.query` will return a new `dojo.NodeList` instance + // constructed from that parameter alone and all further + // processing will stop. This means that if you have a reference + // to a node or NodeList, you can quickly construct a new NodeList + // from the original by calling `dojo.query(node)` or + // `dojo.query(list)`. + // + // query: + // The CSS3 expression to match against. For details on the syntax of + // CSS3 selectors, see + // root: + // A DOMNode (or node id) to scope the search from. Optional. + // returns: dojo.NodeList + // An instance of `dojo.NodeList`. Many methods are available on + // NodeLists for searching, iterating, manipulating, and handling + // events on the matched nodes in the returned list. + // example: + // search the entire document for elements with the class "foo": + // | dojo.query(".foo"); + // these elements will match: + // | + // | + // |

    + // example: + // search the entire document for elements with the classes "foo" *and* "bar": + // | dojo.query(".foo.bar"); + // these elements will match: + // | + // while these will not: + // | + // |

    + // example: + // find `` elements which are descendants of paragraphs and + // which have a "highlighted" class: + // | dojo.query("p span.highlighted"); + // the innermost span in this fragment matches: + // |

    + // | ... + // | ... + // | + // |

    + // example: + // set an "odd" class on all odd table rows inside of the table + // `#tabular_data`, using the `>` (direct child) selector to avoid + // affecting any nested tables: + // | dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd"); + // example: + // remove all elements with the class "error" from the document + // and store them in a list: + // | var errors = dojo.query(".error").orphan(); + // example: + // add an onclick handler to every submit button in the document + // which causes the form to be sent via Ajax instead: + // | dojo.query("input[type='submit']").onclick(function(e){ + // | dojo.stopEvent(e); // prevent sending the form + // | var btn = e.target; + // | dojo.xhrPost({ + // | form: btn.form, + // | load: function(data){ + // | // replace the form with the response + // | var div = dojo.doc.createElement("div"); + // | dojo.place(div, btn.form, "after"); + // | div.innerHTML = data; + // | dojo.style(btn.form, "display", "none"); + // | } + // | }); + // | }); + + //Set list constructor to desired value. This can change + //between calls, so always re-assign here. + qlc = d._NodeListCtor; + + if(!query){ + return new qlc(); + } + + if(query.constructor == qlc){ + return query; + } + if(typeof query != "string"){ // inline'd type check + return new qlc(query); // dojo.NodeList + } + if(typeof root == "string"){ // inline'd type check + root = d.byId(root); + if(!root){ return new qlc(); } + } + + root = root||getDoc(); + var od = root.ownerDocument||root.documentElement; + + // throw the big case sensitivity switch + + // NOTE: + // Opera in XHTML mode doesn't detect case-sensitivity correctly + // and it's not clear that there's any way to test for it + caseSensitive = (root.contentType && root.contentType=="application/xml") || + (d.isOpera && (root.doctype || od.toString() == "[object XMLDocument]")) || + (!!od) && + (d.isIE ? od.xml : (root.xmlVersion||od.xmlVersion)); + + // NOTE: + // adding "true" as the 2nd argument to getQueryFunc is useful for + // testing the DOM branch without worrying about the + // behavior/performance of the QSA branch. + var r = getQueryFunc(query)(root); + + // FIXME: + // need to investigate this branch WRT #8074 and #8075 + if(r && r.nozip && !qlc._wrap){ + return r; + } + return _zip(r); // dojo.NodeList + } + + // FIXME: need to add infrastructure for post-filtering pseudos, ala :last + d.query.pseudos = pseudos; + + // one-off function for filtering a NodeList based on a simple selector + d._filterQueryResult = function(nodeList, simpleFilter){ + var tmpNodeList = new d._NodeListCtor(); + var filterFunc = getSimpleFilterFunc(getQueryParts(simpleFilter)[0]); + for(var x = 0, te; te = nodeList[x]; x++){ + if(filterFunc(te)){ tmpNodeList.push(te); } + } + return tmpNodeList; + } })(this["queryPortability"]||this["acme"]||dojo); + +/* +*/ + } diff --git a/lib/dojo/_base/window.js b/lib/dojo/_base/window.js index 44239d92..5c6e2e95 100644 --- a/lib/dojo/_base/window.js +++ b/lib/dojo/_base/window.js @@ -5,45 +5,104 @@ */ -if(!dojo._hasResource["dojo._base.window"]){ -dojo._hasResource["dojo._base.window"]=true; +if(!dojo._hasResource["dojo._base.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.window"] = true; dojo.provide("dojo._base.window"); -dojo.doc=window["document"]||null; -dojo.body=function(){ -return dojo.doc.body||dojo.doc.getElementsByTagName("body")[0]; -}; -dojo.setContext=function(_1,_2){ -dojo.global=_1; -dojo.doc=_2; -}; -dojo.withGlobal=function(_3,_4,_5,_6){ -var _7=dojo.global; -try{ -dojo.global=_3; -return dojo.withDoc.call(null,_3.document,_4,_5,_6); + +/*===== +dojo.doc = { + // summary: + // Alias for the current document. 'dojo.doc' can be modified + // for temporary context shifting. Also see dojo.withDoc(). + // description: + // Refer to dojo.doc rather + // than referring to 'window.document' to ensure your code runs + // correctly in managed contexts. + // example: + // | n.appendChild(dojo.doc.createElement('div')); } -finally{ -dojo.global=_7; +=====*/ +dojo.doc = window["document"] || null; + +dojo.body = function(){ + // summary: + // Return the body element of the document + // return the body object associated with dojo.doc + // example: + // | dojo.body().appendChild(dojo.doc.createElement('div')); + + // Note: document.body is not defined for a strict xhtml document + // Would like to memoize this, but dojo.doc can change vi dojo.withDoc(). + return dojo.doc.body || dojo.doc.getElementsByTagName("body")[0]; // Node } + +dojo.setContext = function(/*Object*/globalObject, /*DocumentElement*/globalDocument){ + // summary: + // changes the behavior of many core Dojo functions that deal with + // namespace and DOM lookup, changing them to work in a new global + // context (e.g., an iframe). The varibles dojo.global and dojo.doc + // are modified as a result of calling this function and the result of + // `dojo.body()` likewise differs. + dojo.global = globalObject; + dojo.doc = globalDocument; }; -dojo.withDoc=function(_8,_9,_a,_b){ -var _c=dojo.doc,_d=dojo._bodyLtr,_e=dojo.isQuirks; -try{ -dojo.doc=_8; -delete dojo._bodyLtr; -dojo.isQuirks=dojo.doc.compatMode=="BackCompat"; -if(_a&&typeof _9=="string"){ -_9=_a[_9]; -} -return _9.apply(_a,_b||[]); -} -finally{ -dojo.doc=_c; -delete dojo._bodyLtr; -if(_d!==undefined){ -dojo._bodyLtr=_d; -} -dojo.isQuirks=_e; + +dojo.withGlobal = function( /*Object*/globalObject, + /*Function*/callback, + /*Object?*/thisObject, + /*Array?*/cbArguments){ + // summary: + // Invoke callback with globalObject as dojo.global and + // globalObject.document as dojo.doc. + // description: + // Invoke callback with globalObject as dojo.global and + // globalObject.document as dojo.doc. If provided, globalObject + // will be executed in the context of object thisObject + // When callback() returns or throws an error, the dojo.global + // and dojo.doc will be restored to its previous state. + + var oldGlob = dojo.global; + try{ + dojo.global = globalObject; + return dojo.withDoc.call(null, globalObject.document, callback, thisObject, cbArguments); + }finally{ + dojo.global = oldGlob; + } } + +dojo.withDoc = function( /*DocumentElement*/documentObject, + /*Function*/callback, + /*Object?*/thisObject, + /*Array?*/cbArguments){ + // summary: + // Invoke callback with documentObject as dojo.doc. + // description: + // Invoke callback with documentObject as dojo.doc. If provided, + // callback will be executed in the context of object thisObject + // When callback() returns or throws an error, the dojo.doc will + // be restored to its previous state. + + var oldDoc = dojo.doc, + oldLtr = dojo._bodyLtr, + oldQ = dojo.isQuirks; + + try{ + dojo.doc = documentObject; + delete dojo._bodyLtr; // uncache + dojo.isQuirks = dojo.doc.compatMode == "BackCompat"; // no need to check for QuirksMode which was Opera 7 only + + if(thisObject && typeof callback == "string"){ + callback = thisObject[callback]; + } + + return callback.apply(thisObject, cbArguments || []); + }finally{ + dojo.doc = oldDoc; + delete dojo._bodyLtr; // in case it was undefined originally, and set to true/false by the alternate document + if(oldLtr !== undefined){ dojo._bodyLtr = oldLtr; } + dojo.isQuirks = oldQ; + } }; + + } diff --git a/lib/dojo/_base/xhr.js b/lib/dojo/_base/xhr.js index 5823371c..818f8e41 100644 --- a/lib/dojo/_base/xhr.js +++ b/lib/dojo/_base/xhr.js @@ -5,433 +5,937 @@ */ -if(!dojo._hasResource["dojo._base.xhr"]){ -dojo._hasResource["dojo._base.xhr"]=true; +if(!dojo._hasResource["dojo._base.xhr"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._base.xhr"] = true; dojo.provide("dojo._base.xhr"); dojo.require("dojo._base.Deferred"); dojo.require("dojo._base.json"); dojo.require("dojo._base.lang"); dojo.require("dojo._base.query"); + (function(){ -var _1=dojo,_2=_1.config; -function _3(_4,_5,_6){ -if(_6===null){ -return; -} -var _7=_4[_5]; -if(typeof _7=="string"){ -_4[_5]=[_7,_6]; -}else{ -if(_1.isArray(_7)){ -_7.push(_6); -}else{ -_4[_5]=_6; -} -} -}; -dojo.fieldToObject=function(_8){ -var _9=null; -var _a=_1.byId(_8); -if(_a){ -var _b=_a.name; -var _c=(_a.type||"").toLowerCase(); -if(_b&&_c&&!_a.disabled){ -if(_c=="radio"||_c=="checkbox"){ -if(_a.checked){ -_9=_a.value; -} -}else{ -if(_a.multiple){ -_9=[]; -_1.query("option",_a).forEach(function(_d){ -if(_d.selected){ -_9.push(_d.value); -} -}); -}else{ -_9=_a.value; -} -} -} -} -return _9; -}; -dojo.formToObject=function(_e){ -var _f={}; -var _10="file|submit|image|reset|button|"; -_1.forEach(dojo.byId(_e).elements,function(_11){ -var _12=_11.name; -var _13=(_11.type||"").toLowerCase(); -if(_12&&_13&&_10.indexOf(_13)==-1&&!_11.disabled){ -_3(_f,_12,_1.fieldToObject(_11)); -if(_13=="image"){ -_f[_12+".x"]=_f[_12+".y"]=_f[_12].x=_f[_12].y=0; -} -} -}); -return _f; -}; -dojo.objectToQuery=function(map){ -var enc=encodeURIComponent; -var _14=[]; -var _15={}; -for(var _16 in map){ -var _17=map[_16]; -if(_17!=_15[_16]){ -var _18=enc(_16)+"="; -if(_1.isArray(_17)){ -for(var i=0;i<_17.length;i++){ -_14.push(_18+enc(_17[i])); -} -}else{ -_14.push(_18+enc(_17)); -} -} -} -return _14.join("&"); -}; -dojo.formToQuery=function(_19){ -return _1.objectToQuery(_1.formToObject(_19)); -}; -dojo.formToJson=function(_1a,_1b){ -return _1.toJson(_1.formToObject(_1a),_1b); -}; -dojo.queryToObject=function(str){ -var ret={}; -var qp=str.split("&"); -var dec=decodeURIComponent; -_1.forEach(qp,function(_1c){ -if(_1c.length){ -var _1d=_1c.split("="); -var _1e=dec(_1d.shift()); -var val=dec(_1d.join("=")); -if(typeof ret[_1e]=="string"){ -ret[_1e]=[ret[_1e]]; -} -if(_1.isArray(ret[_1e])){ -ret[_1e].push(val); -}else{ -ret[_1e]=val; -} -} -}); -return ret; -}; -dojo._blockAsync=false; -var _1f=_1._contentHandlers=dojo.contentHandlers={text:function(xhr){ -return xhr.responseText; -},json:function(xhr){ -return _1.fromJson(xhr.responseText||null); -},"json-comment-filtered":function(xhr){ -if(!dojo.config.useCommentedJson){ -console.warn("Consider using the standard mimetype:application/json."+" json-commenting can introduce security issues. To"+" decrease the chances of hijacking, use the standard the 'json' handler and"+" prefix your json with: {}&&\n"+"Use djConfig.useCommentedJson=true to turn off this message."); -} -var _20=xhr.responseText; -var _21=_20.indexOf("/*"); -var _22=_20.lastIndexOf("*/"); -if(_21==-1||_22==-1){ -throw new Error("JSON was not comment filtered"); -} -return _1.fromJson(_20.substring(_21+2,_22)); -},javascript:function(xhr){ -return _1.eval(xhr.responseText); -},xml:function(xhr){ -var _23=xhr.responseXML; -if(_1.isIE&&(!_23||!_23.documentElement)){ -var ms=function(n){ -return "MSXML"+n+".DOMDocument"; -}; -var dp=["Microsoft.XMLDOM",ms(6),ms(4),ms(3),ms(2)]; -_1.some(dp,function(p){ -try{ -var dom=new ActiveXObject(p); -dom.async=false; -dom.loadXML(xhr.responseText); -_23=dom; -} -catch(e){ -return false; -} -return true; -}); -} -return _23; -},"json-comment-optional":function(xhr){ -if(xhr.responseText&&/^[^{\[]*\/\*/.test(xhr.responseText)){ -return _1f["json-comment-filtered"](xhr); -}else{ -return _1f["json"](xhr); -} -}}; -dojo._ioSetArgs=function(_24,_25,_26,_27){ -var _28={args:_24,url:_24.url}; -var _29=null; -if(_24.form){ -var _2a=_1.byId(_24.form); -var _2b=_2a.getAttributeNode("action"); -_28.url=_28.url||(_2b?_2b.value:null); -_29=_1.formToObject(_2a); -} -var _2c=[{}]; -if(_29){ -_2c.push(_29); -} -if(_24.content){ -_2c.push(_24.content); -} -if(_24.preventCache){ -_2c.push({"dojo.preventCache":new Date().valueOf()}); -} -_28.query=_1.objectToQuery(_1.mixin.apply(null,_2c)); -_28.handleAs=_24.handleAs||"text"; -var d=new _1.Deferred(_25); -d.addCallbacks(_26,function(_2d){ -return _27(_2d,d); -}); -var ld=_24.load; -if(ld&&_1.isFunction(ld)){ -d.addCallback(function(_2e){ -return ld.call(_24,_2e,_28); -}); -} -var err=_24.error; -if(err&&_1.isFunction(err)){ -d.addErrback(function(_2f){ -return err.call(_24,_2f,_28); -}); -} -var _30=_24.handle; -if(_30&&_1.isFunction(_30)){ -d.addBoth(function(_31){ -return _30.call(_24,_31,_28); -}); -} -if(_2.ioPublish&&_1.publish&&_28.args.ioPublish!==false){ -d.addCallbacks(function(res){ -_1.publish("/dojo/io/load",[d,res]); -return res; -},function(res){ -_1.publish("/dojo/io/error",[d,res]); -return res; -}); -d.addBoth(function(res){ -_1.publish("/dojo/io/done",[d,res]); -return res; -}); -} -d.ioArgs=_28; -return d; -}; -var _32=function(dfd){ -dfd.canceled=true; -var xhr=dfd.ioArgs.xhr; -var _33=typeof xhr.abort; -if(_33=="function"||_33=="object"||_33=="unknown"){ -xhr.abort(); -} -var err=dfd.ioArgs.error; -if(!err){ -err=new Error("xhr cancelled"); -err.dojoType="cancel"; -} -return err; -}; -var _34=function(dfd){ -var ret=_1f[dfd.ioArgs.handleAs](dfd.ioArgs.xhr); -return ret===undefined?null:ret; -}; -var _35=function(_36,dfd){ -if(!dfd.ioArgs.args.failOk){ -console.error(_36); -} -return _36; -}; -var _37=null; -var _38=[]; -var _39=0; -var _3a=function(dfd){ -if(_39<=0){ -_39=0; -if(_2.ioPublish&&_1.publish&&(!dfd||dfd&&dfd.ioArgs.args.ioPublish!==false)){ -_1.publish("/dojo/io/stop"); -} -} -}; -var _3b=function(){ -var now=(new Date()).getTime(); -if(!_1._blockAsync){ -for(var i=0,tif;i<_38.length&&(tif=_38[i]);i++){ -var dfd=tif.dfd; -var _3c=function(){ -if(!dfd||dfd.canceled||!tif.validCheck(dfd)){ -_38.splice(i--,1); -_39-=1; -}else{ -if(tif.ioCheck(dfd)){ -_38.splice(i--,1); -tif.resHandle(dfd); -_39-=1; -}else{ -if(dfd.startTime){ -if(dfd.startTime+(dfd.ioArgs.args.timeout||0)2&&!_48)||"POST|PUT".indexOf(_46.toUpperCase())==-1){ -_1._ioAddQueryToUrl(_49); -} -} -} -} -xhr.open(_46,_49.url,_47.sync!==true,_47.user||undefined,_47.password||undefined); -if(_47.headers){ -for(var hdr in _47.headers){ -if(hdr.toLowerCase()==="content-type"&&!_47.contentType){ -_47.contentType=_47.headers[hdr]; -}else{ -if(_47.headers[hdr]){ -xhr.setRequestHeader(hdr,_47.headers[hdr]); -} -} -} -} -xhr.setRequestHeader("Content-Type",_47.contentType||_41); -if(!_47.headers||!("X-Requested-With" in _47.headers)){ -xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"); -} -_1._ioNotifyStart(dfd); -if(dojo.config.debugAtAllCosts){ -xhr.send(_49.query); -}else{ -try{ -xhr.send(_49.query); -} -catch(e){ -_49.error=e; -dfd.cancel(); -} -} -_1._ioWatch(dfd,_42,_43,_44); -xhr=null; -return dfd; -}; -dojo.xhrGet=function(_4a){ -return _1.xhr("GET",_4a); -}; -dojo.rawXhrPost=dojo.xhrPost=function(_4b){ -return _1.xhr("POST",_4b,true); -}; -dojo.rawXhrPut=dojo.xhrPut=function(_4c){ -return _1.xhr("PUT",_4c,true); -}; -dojo.xhrDelete=function(_4d){ -return _1.xhr("DELETE",_4d); -}; + var _d = dojo, cfg = _d.config; + + function setValue(/*Object*/obj, /*String*/name, /*String*/value){ + //summary: + // For the named property in object, set the value. If a value + // already exists and it is a string, convert the value to be an + // array of values. + + //Skip it if there is no value + if(value === null){ + return; + } + + var val = obj[name]; + if(typeof val == "string"){ // inline'd type check + obj[name] = [val, value]; + }else if(_d.isArray(val)){ + val.push(value); + }else{ + obj[name] = value; + } + } + + dojo.fieldToObject = function(/*DOMNode||String*/ inputNode){ + // summary: + // Serialize a form field to a JavaScript object. + // + // description: + // Returns the value encoded in a form field as + // as a string or an array of strings. Disabled form elements + // and unchecked radio and checkboxes are skipped. Multi-select + // elements are returned as an array of string values. + var ret = null; + var item = _d.byId(inputNode); + if(item){ + var _in = item.name; + var type = (item.type||"").toLowerCase(); + if(_in && type && !item.disabled){ + if(type == "radio" || type == "checkbox"){ + if(item.checked){ ret = item.value } + }else if(item.multiple){ + ret = []; + _d.query("option", item).forEach(function(opt){ + if(opt.selected){ + ret.push(opt.value); + } + }); + }else{ + ret = item.value; + } + } + } + return ret; // Object + } + + dojo.formToObject = function(/*DOMNode||String*/ formNode){ + // summary: + // Serialize a form node to a JavaScript object. + // description: + // Returns the values encoded in an HTML form as + // string properties in an object which it then returns. Disabled form + // elements, buttons, and other non-value form elements are skipped. + // Multi-select elements are returned as an array of string values. + // + // example: + // This form: + // |
    + // | + // | + // | + // | + // |
    + // + // yields this object structure as the result of a call to + // formToObject(): + // + // | { + // | blah: "blah", + // | multi: [ + // | "thud", + // | "thonk" + // | ] + // | }; + + var ret = {}; + var exclude = "file|submit|image|reset|button|"; + _d.forEach(dojo.byId(formNode).elements, function(item){ + var _in = item.name; + var type = (item.type||"").toLowerCase(); + if(_in && type && exclude.indexOf(type) == -1 && !item.disabled){ + setValue(ret, _in, _d.fieldToObject(item)); + if(type == "image"){ + ret[_in+".x"] = ret[_in+".y"] = ret[_in].x = ret[_in].y = 0; + } + } + }); + return ret; // Object + } + + dojo.objectToQuery = function(/*Object*/ map){ + // summary: + // takes a name/value mapping object and returns a string representing + // a URL-encoded version of that object. + // example: + // this object: + // + // | { + // | blah: "blah", + // | multi: [ + // | "thud", + // | "thonk" + // | ] + // | }; + // + // yields the following query string: + // + // | "blah=blah&multi=thud&multi=thonk" + + // FIXME: need to implement encodeAscii!! + var enc = encodeURIComponent; + var pairs = []; + var backstop = {}; + for(var name in map){ + var value = map[name]; + if(value != backstop[name]){ + var assign = enc(name) + "="; + if(_d.isArray(value)){ + for(var i=0; i < value.length; i++){ + pairs.push(assign + enc(value[i])); + } + }else{ + pairs.push(assign + enc(value)); + } + } + } + return pairs.join("&"); // String + } + + dojo.formToQuery = function(/*DOMNode||String*/ formNode){ + // summary: + // Returns a URL-encoded string representing the form passed as either a + // node or string ID identifying the form to serialize + return _d.objectToQuery(_d.formToObject(formNode)); // String + } + + dojo.formToJson = function(/*DOMNode||String*/ formNode, /*Boolean?*/prettyPrint){ + // summary: + // Create a serialized JSON string from a form node or string + // ID identifying the form to serialize + return _d.toJson(_d.formToObject(formNode), prettyPrint); // String + } + + dojo.queryToObject = function(/*String*/ str){ + // summary: + // Create an object representing a de-serialized query section of a + // URL. Query keys with multiple values are returned in an array. + // + // example: + // This string: + // + // | "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&" + // + // results in this object structure: + // + // | { + // | foo: [ "bar", "baz" ], + // | thinger: " spaces =blah", + // | zonk: "blarg" + // | } + // + // Note that spaces and other urlencoded entities are correctly + // handled. + + // FIXME: should we grab the URL string if we're not passed one? + var ret = {}; + var qp = str.split("&"); + var dec = decodeURIComponent; + _d.forEach(qp, function(item){ + if(item.length){ + var parts = item.split("="); + var name = dec(parts.shift()); + var val = dec(parts.join("=")); + if(typeof ret[name] == "string"){ // inline'd type check + ret[name] = [ret[name]]; + } + + if(_d.isArray(ret[name])){ + ret[name].push(val); + }else{ + ret[name] = val; + } + } + }); + return ret; // Object + } + + // need to block async callbacks from snatching this thread as the result + // of an async callback might call another sync XHR, this hangs khtml forever + // must checked by watchInFlight() + + dojo._blockAsync = false; + + // MOW: remove dojo._contentHandlers alias in 2.0 + var handlers = _d._contentHandlers = dojo.contentHandlers = { + // summary: + // A map of availble XHR transport handle types. Name matches the + // `handleAs` attribute passed to XHR calls. + // + // description: + // A map of availble XHR transport handle types. Name matches the + // `handleAs` attribute passed to XHR calls. Each contentHandler is + // called, passing the xhr object for manipulation. The return value + // from the contentHandler will be passed to the `load` or `handle` + // functions defined in the original xhr call. + // + // example: + // Creating a custom content-handler: + // | dojo.contentHandlers.makeCaps = function(xhr){ + // | return xhr.responseText.toUpperCase(); + // | } + // | // and later: + // | dojo.xhrGet({ + // | url:"foo.txt", + // | handleAs:"makeCaps", + // | load: function(data){ /* data is a toUpper version of foo.txt */ } + // | }); + + text: function(xhr){ + // summary: A contentHandler which simply returns the plaintext response data + return xhr.responseText; + }, + json: function(xhr){ + // summary: A contentHandler which returns a JavaScript object created from the response data + return _d.fromJson(xhr.responseText || null); + }, + "json-comment-filtered": function(xhr){ + // summary: A contentHandler which expects comment-filtered JSON. + // description: + // A contentHandler which expects comment-filtered JSON. + // the json-comment-filtered option was implemented to prevent + // "JavaScript Hijacking", but it is less secure than standard JSON. Use + // standard JSON instead. JSON prefixing can be used to subvert hijacking. + // + // Will throw a notice suggesting to use application/json mimetype, as + // json-commenting can introduce security issues. To decrease the chances of hijacking, + // use the standard `json` contentHandler, and prefix your "JSON" with: {}&& + // + // use djConfig.useCommentedJson = true to turn off the notice + if(!dojo.config.useCommentedJson){ + console.warn("Consider using the standard mimetype:application/json." + + " json-commenting can introduce security issues. To" + + " decrease the chances of hijacking, use the standard the 'json' handler and" + + " prefix your json with: {}&&\n" + + "Use djConfig.useCommentedJson=true to turn off this message."); + } + + var value = xhr.responseText; + var cStartIdx = value.indexOf("\/*"); + var cEndIdx = value.lastIndexOf("*\/"); + if(cStartIdx == -1 || cEndIdx == -1){ + throw new Error("JSON was not comment filtered"); + } + return _d.fromJson(value.substring(cStartIdx+2, cEndIdx)); + }, + javascript: function(xhr){ + // summary: A contentHandler which evaluates the response data, expecting it to be valid JavaScript + + // FIXME: try Moz and IE specific eval variants? + return _d.eval(xhr.responseText); + }, + xml: function(xhr){ + // summary: A contentHandler returning an XML Document parsed from the response data + var result = xhr.responseXML; + if(_d.isIE && (!result || !result.documentElement)){ + //WARNING: this branch used by the xml handling in dojo.io.iframe, + //so be sure to test dojo.io.iframe if making changes below. + var ms = function(n){ return "MSXML" + n + ".DOMDocument"; } + var dp = ["Microsoft.XMLDOM", ms(6), ms(4), ms(3), ms(2)]; + _d.some(dp, function(p){ + try{ + var dom = new ActiveXObject(p); + dom.async = false; + dom.loadXML(xhr.responseText); + result = dom; + }catch(e){ return false; } + return true; + }); + } + return result; // DOMDocument + }, + "json-comment-optional": function(xhr){ + // summary: A contentHandler which checks the presence of comment-filtered JSON and + // alternates between the `json` and `json-comment-filtered` contentHandlers. + if(xhr.responseText && /^[^{\[]*\/\*/.test(xhr.responseText)){ + return handlers["json-comment-filtered"](xhr); + }else{ + return handlers["json"](xhr); + } + } + }; + + /*===== + dojo.__IoArgs = function(){ + // url: String + // URL to server endpoint. + // content: Object? + // Contains properties with string values. These + // properties will be serialized as name1=value2 and + // passed in the request. + // timeout: Integer? + // Milliseconds to wait for the response. If this time + // passes, the then error callbacks are called. + // form: DOMNode? + // DOM node for a form. Used to extract the form values + // and send to the server. + // preventCache: Boolean? + // Default is false. If true, then a + // "dojo.preventCache" parameter is sent in the request + // with a value that changes with each request + // (timestamp). Useful only with GET-type requests. + // handleAs: String? + // Acceptable values depend on the type of IO + // transport (see specific IO calls for more information). + // rawBody: String? + // Sets the raw body for an HTTP request. If this is used, then the content + // property is ignored. This is mostly useful for HTTP methods that have + // a body to their requests, like PUT or POST. This property can be used instead + // of postData and putData for dojo.rawXhrPost and dojo.rawXhrPut respectively. + // ioPublish: Boolean? + // Set this explicitly to false to prevent publishing of topics related to + // IO operations. Otherwise, if djConfig.ioPublish is set to true, topics + // will be published via dojo.publish for different phases of an IO operation. + // See dojo.__IoPublish for a list of topics that are published. + // load: Function? + // This function will be + // called on a successful HTTP response code. + // error: Function? + // This function will + // be called when the request fails due to a network or server error, the url + // is invalid, etc. It will also be called if the load or handle callback throws an + // exception, unless djConfig.debugAtAllCosts is true. This allows deployed applications + // to continue to run even when a logic error happens in the callback, while making + // it easier to troubleshoot while in debug mode. + // handle: Function? + // This function will + // be called at the end of every request, whether or not an error occurs. + this.url = url; + this.content = content; + this.timeout = timeout; + this.form = form; + this.preventCache = preventCache; + this.handleAs = handleAs; + this.ioPublish = ioPublish; + this.load = function(response, ioArgs){ + // ioArgs: dojo.__IoCallbackArgs + // Provides additional information about the request. + // response: Object + // The response in the format as defined with handleAs. + } + this.error = function(response, ioArgs){ + // ioArgs: dojo.__IoCallbackArgs + // Provides additional information about the request. + // response: Object + // The response in the format as defined with handleAs. + } + this.handle = function(loadOrError, response, ioArgs){ + // loadOrError: String + // Provides a string that tells you whether this function + // was called because of success (load) or failure (error). + // response: Object + // The response in the format as defined with handleAs. + // ioArgs: dojo.__IoCallbackArgs + // Provides additional information about the request. + } + } + =====*/ + + /*===== + dojo.__IoCallbackArgs = function(args, xhr, url, query, handleAs, id, canDelete, json){ + // args: Object + // the original object argument to the IO call. + // xhr: XMLHttpRequest + // For XMLHttpRequest calls only, the + // XMLHttpRequest object that was used for the + // request. + // url: String + // The final URL used for the call. Many times it + // will be different than the original args.url + // value. + // query: String + // For non-GET requests, the + // name1=value1&name2=value2 parameters sent up in + // the request. + // handleAs: String + // The final indicator on how the response will be + // handled. + // id: String + // For dojo.io.script calls only, the internal + // script ID used for the request. + // canDelete: Boolean + // For dojo.io.script calls only, indicates + // whether the script tag that represents the + // request can be deleted after callbacks have + // been called. Used internally to know when + // cleanup can happen on JSONP-type requests. + // json: Object + // For dojo.io.script calls only: holds the JSON + // response for JSONP-type requests. Used + // internally to hold on to the JSON responses. + // You should not need to access it directly -- + // the same object should be passed to the success + // callbacks directly. + this.args = args; + this.xhr = xhr; + this.url = url; + this.query = query; + this.handleAs = handleAs; + this.id = id; + this.canDelete = canDelete; + this.json = json; + } + =====*/ + + + /*===== + dojo.__IoPublish = function(){ + // summary: + // This is a list of IO topics that can be published + // if djConfig.ioPublish is set to true. IO topics can be + // published for any Input/Output, network operation. So, + // dojo.xhr, dojo.io.script and dojo.io.iframe can all + // trigger these topics to be published. + // start: String + // "/dojo/io/start" is sent when there are no outstanding IO + // requests, and a new IO request is started. No arguments + // are passed with this topic. + // send: String + // "/dojo/io/send" is sent whenever a new IO request is started. + // It passes the dojo.Deferred for the request with the topic. + // load: String + // "/dojo/io/load" is sent whenever an IO request has loaded + // successfully. It passes the response and the dojo.Deferred + // for the request with the topic. + // error: String + // "/dojo/io/error" is sent whenever an IO request has errored. + // It passes the error and the dojo.Deferred + // for the request with the topic. + // done: String + // "/dojo/io/done" is sent whenever an IO request has completed, + // either by loading or by erroring. It passes the error and + // the dojo.Deferred for the request with the topic. + // stop: String + // "/dojo/io/stop" is sent when all outstanding IO requests have + // finished. No arguments are passed with this topic. + this.start = "/dojo/io/start"; + this.send = "/dojo/io/send"; + this.load = "/dojo/io/load"; + this.error = "/dojo/io/error"; + this.done = "/dojo/io/done"; + this.stop = "/dojo/io/stop"; + } + =====*/ + + + dojo._ioSetArgs = function(/*dojo.__IoArgs*/args, + /*Function*/canceller, + /*Function*/okHandler, + /*Function*/errHandler){ + // summary: + // sets up the Deferred and ioArgs property on the Deferred so it + // can be used in an io call. + // args: + // The args object passed into the public io call. Recognized properties on + // the args object are: + // canceller: + // The canceller function used for the Deferred object. The function + // will receive one argument, the Deferred object that is related to the + // canceller. + // okHandler: + // The first OK callback to be registered with Deferred. It has the opportunity + // to transform the OK response. It will receive one argument -- the Deferred + // object returned from this function. + // errHandler: + // The first error callback to be registered with Deferred. It has the opportunity + // to do cleanup on an error. It will receive two arguments: error (the + // Error object) and dfd, the Deferred object returned from this function. + + var ioArgs = {args: args, url: args.url}; + + //Get values from form if requestd. + var formObject = null; + if(args.form){ + var form = _d.byId(args.form); + //IE requires going through getAttributeNode instead of just getAttribute in some form cases, + //so use it for all. See #2844 + var actnNode = form.getAttributeNode("action"); + ioArgs.url = ioArgs.url || (actnNode ? actnNode.value : null); + formObject = _d.formToObject(form); + } + + // set up the query params + var miArgs = [{}]; + + if(formObject){ + // potentially over-ride url-provided params w/ form values + miArgs.push(formObject); + } + if(args.content){ + // stuff in content over-rides what's set by form + miArgs.push(args.content); + } + if(args.preventCache){ + miArgs.push({"dojo.preventCache": new Date().valueOf()}); + } + ioArgs.query = _d.objectToQuery(_d.mixin.apply(null, miArgs)); + + // .. and the real work of getting the deferred in order, etc. + ioArgs.handleAs = args.handleAs || "text"; + var d = new _d.Deferred(canceller); + d.addCallbacks(okHandler, function(error){ + return errHandler(error, d); + }); + + //Support specifying load, error and handle callback functions from the args. + //For those callbacks, the "this" object will be the args object. + //The callbacks will get the deferred result value as the + //first argument and the ioArgs object as the second argument. + var ld = args.load; + if(ld && _d.isFunction(ld)){ + d.addCallback(function(value){ + return ld.call(args, value, ioArgs); + }); + } + var err = args.error; + if(err && _d.isFunction(err)){ + d.addErrback(function(value){ + return err.call(args, value, ioArgs); + }); + } + var handle = args.handle; + if(handle && _d.isFunction(handle)){ + d.addBoth(function(value){ + return handle.call(args, value, ioArgs); + }); + } + + //Plug in topic publishing, if dojo.publish is loaded. + if(cfg.ioPublish && _d.publish && ioArgs.args.ioPublish !== false){ + d.addCallbacks( + function(res){ + _d.publish("/dojo/io/load", [d, res]); + return res; + }, + function(res){ + _d.publish("/dojo/io/error", [d, res]); + return res; + } + ); + d.addBoth(function(res){ + _d.publish("/dojo/io/done", [d, res]); + return res; + }); + } + + d.ioArgs = ioArgs; + + // FIXME: need to wire up the xhr object's abort method to something + // analagous in the Deferred + return d; + } + + var _deferredCancel = function(/*Deferred*/dfd){ + // summary: canceller function for dojo._ioSetArgs call. + + dfd.canceled = true; + var xhr = dfd.ioArgs.xhr; + var _at = typeof xhr.abort; + if(_at == "function" || _at == "object" || _at == "unknown"){ + xhr.abort(); + } + var err = dfd.ioArgs.error; + if(!err){ + err = new Error("xhr cancelled"); + err.dojoType="cancel"; + } + return err; + } + var _deferredOk = function(/*Deferred*/dfd){ + // summary: okHandler function for dojo._ioSetArgs call. + + var ret = handlers[dfd.ioArgs.handleAs](dfd.ioArgs.xhr); + return ret === undefined ? null : ret; + } + var _deferError = function(/*Error*/error, /*Deferred*/dfd){ + // summary: errHandler function for dojo._ioSetArgs call. + + if(!dfd.ioArgs.args.failOk){ + console.error(error); + } + return error; + } + + // avoid setting a timer per request. It degrades performance on IE + // something fierece if we don't use unified loops. + var _inFlightIntvl = null; + var _inFlight = []; + + + //Use a separate count for knowing if we are starting/stopping io calls. + //Cannot use _inFlight.length since it can change at a different time than + //when we want to do this kind of test. We only want to decrement the count + //after a callback/errback has finished, since the callback/errback should be + //considered as part of finishing a request. + var _pubCount = 0; + var _checkPubCount = function(dfd){ + if(_pubCount <= 0){ + _pubCount = 0; + if(cfg.ioPublish && _d.publish && (!dfd || dfd && dfd.ioArgs.args.ioPublish !== false)){ + _d.publish("/dojo/io/stop"); + } + } + }; + + var _watchInFlight = function(){ + //summary: + // internal method that checks each inflight XMLHttpRequest to see + // if it has completed or if the timeout situation applies. + + var now = (new Date()).getTime(); + // make sure sync calls stay thread safe, if this callback is called + // during a sync call and this results in another sync call before the + // first sync call ends the browser hangs + if(!_d._blockAsync){ + // we need manual loop because we often modify _inFlight (and therefore 'i') while iterating + // note: the second clause is an assigment on purpose, lint may complain + for(var i = 0, tif; i < _inFlight.length && (tif = _inFlight[i]); i++){ + var dfd = tif.dfd; + var func = function(){ + if(!dfd || dfd.canceled || !tif.validCheck(dfd)){ + _inFlight.splice(i--, 1); + _pubCount -= 1; + }else if(tif.ioCheck(dfd)){ + _inFlight.splice(i--, 1); + tif.resHandle(dfd); + _pubCount -= 1; + }else if(dfd.startTime){ + //did we timeout? + if(dfd.startTime + (dfd.ioArgs.args.timeout || 0) < now){ + _inFlight.splice(i--, 1); + var err = new Error("timeout exceeded"); + err.dojoType = "timeout"; + dfd.errback(err); + //Cancel the request so the io module can do appropriate cleanup. + dfd.cancel(); + _pubCount -= 1; + } + } + }; + if(dojo.config.debugAtAllCosts){ + func.call(this); + }else{ + try{ + func.call(this); + }catch(e){ + dfd.errback(e); + } + } + } + } + + _checkPubCount(dfd); + + if(!_inFlight.length){ + clearInterval(_inFlightIntvl); + _inFlightIntvl = null; + return; + } + } + + dojo._ioCancelAll = function(){ + //summary: Cancels all pending IO requests, regardless of IO type + //(xhr, script, iframe). + try{ + _d.forEach(_inFlight, function(i){ + try{ + i.dfd.cancel(); + }catch(e){/*squelch*/} + }); + }catch(e){/*squelch*/} + } + + //Automatically call cancel all io calls on unload + //in IE for trac issue #2357. + if(_d.isIE){ + _d.addOnWindowUnload(_d._ioCancelAll); + } + + _d._ioNotifyStart = function(/*Deferred*/dfd){ + // summary: + // If dojo.publish is available, publish topics + // about the start of a request queue and/or the + // the beginning of request. + // description: + // Used by IO transports. An IO transport should + // call this method before making the network connection. + if(cfg.ioPublish && _d.publish && dfd.ioArgs.args.ioPublish !== false){ + if(!_pubCount){ + _d.publish("/dojo/io/start"); + } + _pubCount += 1; + _d.publish("/dojo/io/send", [dfd]); + } + } + + _d._ioWatch = function(dfd, validCheck, ioCheck, resHandle){ + // summary: + // Watches the io request represented by dfd to see if it completes. + // dfd: Deferred + // The Deferred object to watch. + // validCheck: Function + // Function used to check if the IO request is still valid. Gets the dfd + // object as its only argument. + // ioCheck: Function + // Function used to check if basic IO call worked. Gets the dfd + // object as its only argument. + // resHandle: Function + // Function used to process response. Gets the dfd + // object as its only argument. + var args = dfd.ioArgs.args; + if(args.timeout){ + dfd.startTime = (new Date()).getTime(); + } + + _inFlight.push({dfd: dfd, validCheck: validCheck, ioCheck: ioCheck, resHandle: resHandle}); + if(!_inFlightIntvl){ + _inFlightIntvl = setInterval(_watchInFlight, 50); + } + // handle sync requests + //A weakness: async calls in flight + //could have their handlers called as part of the + //_watchInFlight call, before the sync's callbacks + // are called. + if(args.sync){ + _watchInFlight(); + } + } + + var _defaultContentType = "application/x-www-form-urlencoded"; + + var _validCheck = function(/*Deferred*/dfd){ + return dfd.ioArgs.xhr.readyState; //boolean + } + var _ioCheck = function(/*Deferred*/dfd){ + return 4 == dfd.ioArgs.xhr.readyState; //boolean + } + var _resHandle = function(/*Deferred*/dfd){ + var xhr = dfd.ioArgs.xhr; + if(_d._isDocumentOk(xhr)){ + dfd.callback(dfd); + }else{ + var err = new Error("Unable to load " + dfd.ioArgs.url + " status:" + xhr.status); + err.status = xhr.status; + err.responseText = xhr.responseText; + dfd.errback(err); + } + } + + dojo._ioAddQueryToUrl = function(/*dojo.__IoCallbackArgs*/ioArgs){ + //summary: Adds query params discovered by the io deferred construction to the URL. + //Only use this for operations which are fundamentally GET-type operations. + if(ioArgs.query.length){ + ioArgs.url += (ioArgs.url.indexOf("?") == -1 ? "?" : "&") + ioArgs.query; + ioArgs.query = null; + } + } + + /*===== + dojo.declare("dojo.__XhrArgs", dojo.__IoArgs, { + constructor: function(){ + // summary: + // In addition to the properties listed for the dojo._IoArgs type, + // the following properties are allowed for dojo.xhr* methods. + // handleAs: String? + // Acceptable values are: text (default), json, json-comment-optional, + // json-comment-filtered, javascript, xml. See `dojo.contentHandlers` + // sync: Boolean? + // false is default. Indicates whether the request should + // be a synchronous (blocking) request. + // headers: Object? + // Additional HTTP headers to send in the request. + // failOk: Boolean? + // false is default. Indicates whether a request should be + // allowed to fail (and therefore no console error message in + // the event of a failure) + this.handleAs = handleAs; + this.sync = sync; + this.headers = headers; + this.failOk = failOk; + } + }); + =====*/ + + dojo.xhr = function(/*String*/ method, /*dojo.__XhrArgs*/ args, /*Boolean?*/ hasBody){ + // summary: + // Sends an HTTP request with the given method. + // description: + // Sends an HTTP request with the given method. + // See also dojo.xhrGet(), xhrPost(), xhrPut() and dojo.xhrDelete() for shortcuts + // for those HTTP methods. There are also methods for "raw" PUT and POST methods + // via dojo.rawXhrPut() and dojo.rawXhrPost() respectively. + // method: + // HTTP method to be used, such as GET, POST, PUT, DELETE. Should be uppercase. + // hasBody: + // If the request has an HTTP body, then pass true for hasBody. + + //Make the Deferred object for this xhr request. + var dfd = _d._ioSetArgs(args, _deferredCancel, _deferredOk, _deferError); + var ioArgs = dfd.ioArgs; + + //Pass the args to _xhrObj, to allow alternate XHR calls based specific calls, like + //the one used for iframe proxies. + var xhr = ioArgs.xhr = _d._xhrObj(ioArgs.args); + //If XHR factory fails, cancel the deferred. + if(!xhr){ + dfd.cancel(); + return dfd; + } + + //Allow for specifying the HTTP body completely. + if("postData" in args){ + ioArgs.query = args.postData; + }else if("putData" in args){ + ioArgs.query = args.putData; + }else if("rawBody" in args){ + ioArgs.query = args.rawBody; + }else if((arguments.length > 2 && !hasBody) || "POST|PUT".indexOf(method.toUpperCase()) == -1){ + //Check for hasBody being passed. If no hasBody, + //then only append query string if not a POST or PUT request. + _d._ioAddQueryToUrl(ioArgs); + } + + // IE 6 is a steaming pile. It won't let you call apply() on the native function (xhr.open). + // workaround for IE6's apply() "issues" + xhr.open(method, ioArgs.url, args.sync !== true, args.user || undefined, args.password || undefined); + if(args.headers){ + for(var hdr in args.headers){ + if(hdr.toLowerCase() === "content-type" && !args.contentType){ + args.contentType = args.headers[hdr]; + }else if(args.headers[hdr]){ + //Only add header if it has a value. This allows for instnace, skipping + //insertion of X-Requested-With by specifying empty value. + xhr.setRequestHeader(hdr, args.headers[hdr]); + } + } + } + // FIXME: is this appropriate for all content types? + xhr.setRequestHeader("Content-Type", args.contentType || _defaultContentType); + if(!args.headers || !("X-Requested-With" in args.headers)){ + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + } + // FIXME: set other headers here! + _d._ioNotifyStart(dfd); + if(dojo.config.debugAtAllCosts){ + xhr.send(ioArgs.query); + }else{ + try{ + xhr.send(ioArgs.query); + }catch(e){ + ioArgs.error = e; + dfd.cancel(); + } + } + _d._ioWatch(dfd, _validCheck, _ioCheck, _resHandle); + xhr = null; + return dfd; // dojo.Deferred + } + + dojo.xhrGet = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP GET request to the server. + return _d.xhr("GET", args); // dojo.Deferred + } + + dojo.rawXhrPost = dojo.xhrPost = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP POST request to the server. In addtion to the properties + // listed for the dojo.__XhrArgs type, the following property is allowed: + // postData: + // String. Send raw data in the body of the POST request. + return _d.xhr("POST", args, true); // dojo.Deferred + } + + dojo.rawXhrPut = dojo.xhrPut = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP PUT request to the server. In addtion to the properties + // listed for the dojo.__XhrArgs type, the following property is allowed: + // putData: + // String. Send raw data in the body of the PUT request. + return _d.xhr("PUT", args, true); // dojo.Deferred + } + + dojo.xhrDelete = function(/*dojo.__XhrArgs*/ args){ + // summary: + // Sends an HTTP DELETE request to the server. + return _d.xhr("DELETE", args); //dojo.Deferred + } + + /* + dojo.wrapForm = function(formNode){ + //summary: + // A replacement for FormBind, but not implemented yet. + + // FIXME: need to think harder about what extensions to this we might + // want. What should we allow folks to do w/ this? What events to + // set/send? + throw new Error("dojo.wrapForm not yet implemented"); + } + */ })(); + } diff --git a/lib/dojo/_firebug/firebug.css b/lib/dojo/_firebug/firebug.css index 3b6f4f99..27657ef4 100644 --- a/lib/dojo/_firebug/firebug.css +++ b/lib/dojo/_firebug/firebug.css @@ -25,6 +25,7 @@ background:#f0f0f0; } + .firebug #firebugLog, .firebug #objectLog { overflow: auto; position: absolute; @@ -171,6 +172,8 @@ .firebug .propertyName { font-weight: bold; } + +/* tabs */ #firebugToolbar ul.tabs{ margin:0 !important; padding:0; @@ -205,4 +208,4 @@ text-decoration:none; background:transparent url(tab_rgt_over.png) no-repeat right; color:#FFFFFF; -} +} \ No newline at end of file diff --git a/lib/dojo/_firebug/firebug.js b/lib/dojo/_firebug/firebug.js index 7736258b..d1f112f6 100644 --- a/lib/dojo/_firebug/firebug.js +++ b/lib/dojo/_firebug/firebug.js @@ -5,910 +5,1222 @@ */ -if(!dojo._hasResource["dojo._firebug.firebug"]){ -dojo._hasResource["dojo._firebug.firebug"]=true; +if(!dojo._hasResource["dojo._firebug.firebug"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo._firebug.firebug"] = true; dojo.provide("dojo._firebug.firebug"); -dojo.deprecated=function(_1,_2,_3){ -var _4="DEPRECATED: "+_1; -if(_2){ -_4+=" "+_2; -} -if(_3){ -_4+=" -- will be removed in version: "+_3; -} -console.warn(_4); + +dojo.deprecated = function(/*String*/ behaviour, /*String?*/ extra, /*String?*/ removal){ + // summary: + // Log a debug message to indicate that a behavior has been + // deprecated. + // extra: Text to append to the message. + // removal: + // Text to indicate when in the future the behavior will be removed. + var message = "DEPRECATED: " + behaviour; + if(extra){ message += " " + extra; } + if(removal){ message += " -- will be removed in version: " + removal; } + console.warn(message); }; -dojo.experimental=function(_5,_6){ -var _7="EXPERIMENTAL: "+_5+" -- APIs subject to change without notice."; -if(_6){ -_7+=" "+_6; -} -console.warn(_7); + +dojo.experimental = function(/* String */ moduleName, /* String? */ extra){ + // summary: Marks code as experimental. + // description: + // This can be used to mark a function, file, or module as + // experimental. Experimental code is not ready to be used, and the + // APIs are subject to change without notice. Experimental code may be + // completed deleted without going through the normal deprecation + // process. + // moduleName: + // The name of a module, or the name of a module file or a specific + // function + // extra: + // some additional message for the user + // example: + // | dojo.experimental("dojo.data.Result"); + // example: + // | dojo.experimental("dojo.weather.toKelvin()", "PENDING approval from NOAA"); + var message = "EXPERIMENTAL: " + moduleName + " -- APIs subject to change without notice."; + if(extra){ message += " " + extra; } + console.warn(message); }; + +// FIREBUG LITE + // summary: Firebug Lite, the baby brother to Joe Hewitt's Firebug for Mozilla Firefox + // description: + // Opens a console for logging, debugging, and error messages. + // Contains partial functionality to Firebug. See function list below. + // NOTE: + // Firebug is a Firefox extension created by Joe Hewitt (see license). You do not need Dojo to run Firebug. + // Firebug Lite is included in Dojo by permission from Joe Hewitt + // If you are new to Firebug, or used to the Dojo 0.4 dojo.debug, you can learn Firebug + // functionality by reading the function comments below or visiting http://www.getfirebug.com/docs.html + // NOTE: + // To test Firebug Lite in Firefox: + // FF2: set "console = null" before loading dojo and set djConfig.isDebug=true + // FF3: disable Firebug and set djConfig.isDebug=true + // + // example: + // Supports inline objects in object inspector window (only simple trace of dom nodes, however) + // | console.log("my object", {foo:"bar"}) + // example: + // Option for console to open in popup window + // | var djConfig = {isDebug: true, popup:true }; + // example: + // Option for console height (ignored for popup) + // | var djConfig = {isDebug: true, debugHeight:100 } + + + (function(){ -var _8=(/Trident/.test(window.navigator.userAgent)); -if(_8){ -var _9=["log","info","debug","warn","error"]; -for(var i=0;i<_9.length;i++){ -var m=_9[i]; -var n="_"+_9[i]; -console[n]=console[m]; -console[m]=(function(){ -var _a=n; -return function(){ -console[_a](Array.prototype.slice.call(arguments).join(" ")); -}; -})(); -} -try{ -console.clear(); -} -catch(e){ -} -} -if(!dojo.isFF&&(!dojo.isChrome||dojo.isChrome<3)&&(!dojo.isSafari||dojo.isSafari<4)&&!_8&&!window.firebug&&(typeof console!="undefined"&&!console.firebug)&&!dojo.config.useCustomLogger&&!dojo.isAIR){ -try{ -if(window!=window.parent){ -if(window.parent["console"]){ -window.console=window.parent.console; -} -return; -} -} -catch(e){ -} -var _b=document; -var _c=window; -var _d=0; -var _e=null; -var _f=null; -var _10=null; -var _11=null; -var _12=null; -var _13=null; -var _14=false; -var _15=[]; -var _16=[]; -var _17={}; -var _18={}; -var _19=null; -var _1a; -var _1b; -var _1c=false; -var _1d=null; -var _1e=document.createElement("div"); -var _1f; -var _20; -window.console={_connects:[],log:function(){ -_21(arguments,""); -},debug:function(){ -_21(arguments,"debug"); -},info:function(){ -_21(arguments,"info"); -},warn:function(){ -_21(arguments,"warning"); -},error:function(){ -_21(arguments,"error"); -},assert:function(_22,_23){ -if(!_22){ -var _24=[]; -for(var i=1;i"); -str=str.replace(/\t/g,"    "); -_26([str],"dir"); -},dirxml:function(_27){ -var _28=[]; -_29(_27,_28); -_26(_28,"dirxml"); -},group:function(){ -_26(arguments,"group",_2a); -},groupEnd:function(){ -_26(arguments,"",_2b); -},time:function(_2c){ -_17[_2c]=new Date().getTime(); -},timeEnd:function(_2d){ -if(_2d in _17){ -var _2e=(new Date()).getTime()-_17[_2d]; -_21([_2d+":",_2e+"ms"]); -delete _17[_2d]; -} -},count:function(_2f){ -if(!_18[_2f]){ -_18[_2f]=0; -} -_18[_2f]++; -_21([_2f+": "+_18[_2f]]); -},trace:function(_30){ -var _31=_30||3; -var f=console.trace.caller; -for(var i=0;i<_31;i++){ -var _32=f.toString(); -var _33=[]; -for(var a=0;a=0&&s.href){ -var h=s.href.replace(/(&|%5C?)forceReload=\d+/,""); -s.href=h+(h.indexOf("?")>=0?"&":"?")+"forceReload="+new Date().valueOf(); -} -} -}}; -function _34(_38){ -_14=_38||!_14; -if(_e){ -_e.style.display=_14?"block":"none"; -} -}; -function _39(){ -_34(true); -if(_12){ -_12.focus(); -} -}; -function _3a(x,y,w,h){ -var win=window.open("","_firebug","status=0,menubar=0,resizable=1,top="+y+",left="+x+",width="+w+",height="+h+",scrollbars=1,addressbar=0"); -if(!win){ -var msg="Firebug Lite could not open a pop-up window, most likely because of a blocker.\n"+"Either enable pop-ups for this domain, or change the djConfig to popup=false."; -alert(msg); -} -_3b(win); -var _3c=win.document; -var _3d="Firebug Lite\n"+"\n"+"
    "+""; -_3c.write(_3d); -_3c.close(); -return win; -}; -function _3b(wn){ -var d=new Date(); -d.setTime(d.getTime()+(60*24*60*60*1000)); -d=d.toUTCString(); -var dc=wn.document,_3e; -if(wn.innerWidth){ -_3e=function(){ -return {w:wn.innerWidth,h:wn.innerHeight}; -}; -}else{ -if(dc.documentElement&&dc.documentElement.clientWidth){ -_3e=function(){ -return {w:dc.documentElement.clientWidth,h:dc.documentElement.clientHeight}; -}; -}else{ -if(dc.body){ -_3e=function(){ -return {w:dc.body.clientWidth,h:dc.body.clientHeight}; -}; -} -} -} -window.onFirebugResize=function(){ -_4c(_3e().h); -clearInterval(wn._firebugWin_resize); -wn._firebugWin_resize=setTimeout(function(){ -var x=wn.screenLeft,y=wn.screenTop,w=wn.outerWidth||wn.document.body.offsetWidth,h=wn.outerHeight||wn.document.body.offsetHeight; -document.cookie="_firebugPosition="+[x,y,w,h].join(",")+"; expires="+d+"; path=/"; -},5000); -}; -}; -function _3f(){ -if(_e){ -return; -} -if(dojo.config.popup){ -var _40="100%"; -var _41=document.cookie.match(/(?:^|; )_firebugPosition=([^;]*)/); -var p=_41?_41[1].split(","):[2,2,320,480]; -_c=_3a(p[0],p[1],p[2],p[3]); -_b=_c.document; -dojo.config.debugContainerId="fb"; -_c.console=window.console; -_c.dojo=window.dojo; -}else{ -_b=document; -_40=(dojo.config.debugHeight||300)+"px"; -} -var _42=_b.createElement("link"); -_42.href=dojo.moduleUrl("dojo._firebug","firebug.css"); -_42.rel="stylesheet"; -_42.type="text/css"; -var _43=_b.getElementsByTagName("head"); -if(_43){ -_43=_43[0]; -} -if(!_43){ -_43=_b.getElementsByTagName("html")[0]; -} -if(dojo.isIE){ -window.setTimeout(function(){ -_43.appendChild(_42); -},0); -}else{ -_43.appendChild(_42); -} -if(dojo.config.debugContainerId){ -_e=_b.getElementById(dojo.config.debugContainerId); -} -if(!_e){ -_e=_b.createElement("div"); -_b.body.appendChild(_e); -} -_e.className+=" firebug"; -_e.style.height=_40; -_e.style.display=(_14?"block":"none"); -var _44=function(_45,_46,_47,_48){ -return "
  • "+_45+"
  • "; -}; -_e.innerHTML="
    "+"
      "+_44("Clear","Remove All Console Logs","clear","")+_44("ReCSS","Refresh CSS without reloading page","recss","")+_44("Console","Show Console Logs","openConsole","gap")+_44("DOM","Show DOM Inspector","openDomInspector","")+_44("Object","Show Object Inspector","openObjectInspector","")+((dojo.config.popup)?"":_44("Close","Close the console","close","gap"))+"\t
    "+"
    "+""+"
    "+"
    Click on an object in the Log display
    "+"
    Hover over HTML elements in the main page. Click to hold selection.
    "; -_13=_b.getElementById("firebugToolbar"); -_12=_b.getElementById("firebugCommandLine"); -_49(_12,"keydown",_4a); -_49(_b,dojo.isIE||dojo.isSafari?"keydown":"keypress",_4b); -_f=_b.getElementById("firebugLog"); -_10=_b.getElementById("objectLog"); -_19=_b.getElementById("domInspect"); -_11=_b.getElementById("fireBugTabs"); -_4c(); -_4d(); -}; -dojo.addOnLoad(_3f); -function _4e(){ -_b=null; -if(_c.console){ -_c.console.clear(); -} -_c=null; -_e=null; -_f=null; -_10=null; -_19=null; -_12=null; -_15=[]; -_16=[]; -_17={}; -}; -function _4f(){ -var _50=_12.value; -_12.value=""; -_26(["> ",_50],"command"); -var _51; -try{ -_51=eval(_50); -} -catch(e){ -} -}; -function _4c(h){ -var _52=25; -var _53=h?h-(_52+_12.offsetHeight+25+(h*0.01))+"px":(_e.offsetHeight-_52-_12.offsetHeight)+"px"; -_f.style.top=_52+"px"; -_f.style.height=_53; -_10.style.height=_53; -_10.style.top=_52+"px"; -_19.style.height=_53; -_19.style.top=_52+"px"; -_12.style.bottom=0; -dojo.addOnWindowUnload(_4e); -}; -function _26(_54,_55,_56){ -if(_f){ -_57(_54,_55,_56); -}else{ -_15.push([_54,_55,_56]); -} -}; -function _4d(){ -var _58=_15; -_15=[]; -for(var i=0;i<_58.length;++i){ -_57(_58[i][0],_58[i][1],_58[i][2]); -} -}; -function _57(_59,_5a,_5b){ -var _5c=_f.scrollTop+_f.offsetHeight>=_f.scrollHeight; -_5b=_5b||_5d; -_5b(_59,_5a); -if(_5c){ -_f.scrollTop=_f.scrollHeight-_f.offsetHeight; -} -}; -function _5e(row){ -var _5f=_16.length?_16[_16.length-1]:_f; -_5f.appendChild(row); -}; -function _5d(_60,_61){ -var row=_f.ownerDocument.createElement("div"); -row.className="logRow"+(_61?" logRow-"+_61:""); -row.innerHTML=_60.join(""); -_5e(row); -}; -function _2a(_62,_63){ -_21(_62,_63); -var _64=_f.ownerDocument.createElement("div"); -_64.className="logGroupBox"; -_5e(_64); -_16.push(_64); -}; -function _2b(){ -_16.pop(); -}; -function _21(_65,_66){ -var _67=[]; -var _68=_65[0]; -var _69=0; -if(typeof (_68)!="string"){ -_68=""; -_69=-1; -} -var _6a=_6b(_68); -for(var i=0;i<_6a.length;++i){ -var _6c=_6a[i]; -if(_6c&&typeof _6c=="object"){ -_6c.appender(_65[++_69],_67); -}else{ -_6d(_6c,_67); -} -} -var ids=[]; -var obs=[]; -for(i=_69+1;i<_65.length;++i){ -_6d(" ",_67); -var _6e=_65[i]; -if(_6e===undefined||_6e===null){ -_6f(_6e,_67); -}else{ -if(typeof (_6e)=="string"){ -_6d(_6e,_67); -}else{ -if(_6e instanceof Date){ -_6d(_6e.toString(),_67); -}else{ -if(_6e.nodeType==9){ -_6d("[ XmlDoc ]",_67); -}else{ -var id="_a"+_d++; -ids.push(id); -obs.push(_6e); -var str=""+_70(_6e)+""; -_71(str,_67); -} -} -} -} -} -_26(_67,_66); -for(i=0;i"; -})); -} -}; -function _6b(_72){ -var _73=[]; -var reg=/((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/; -var _74={s:_6d,d:_75,i:_75,f:_76}; -for(var m=reg.exec(_72);m;m=reg.exec(_72)){ -var _77=m[8]?m[8]:m[5]; -var _78=_77 in _74?_74[_77]:_79; -var _7a=m[3]?parseInt(m[3]):(m[4]=="."?-1:0); -_73.push(_72.substr(0,m[0][0]=="%"?m.index:m.index+1)); -_73.push({appender:_78,precision:_7a}); -_72=_72.substr(m.index+m[0].length); -} -_73.push(_72); -return _73; -}; -function _7b(_7c){ -function _7d(ch){ -switch(ch){ -case "<": -return "<"; -case ">": -return ">"; -case "&": -return "&"; -case "'": -return "'"; -case "\"": -return """; -} -return "?"; -}; -return String(_7c).replace(/[<>&"']/g,_7d); -}; -function _7e(_7f){ -try{ -return _7f+""; -} -catch(e){ -return null; -} -}; -function _71(_80,_81){ -_81.push(_7e(_80)); -}; -function _6d(_82,_83){ -_83.push(_7b(_7e(_82))); -}; -function _6f(_84,_85){ -_85.push("",_7b(_7e(_84)),""); -}; -function _86(_87,_88){ -_88.push(""",_7b(_7e(_87)),"""); -}; -function _75(_89,_8a){ -_8a.push("",_7b(_7e(_89)),""); -}; -function _76(_8b,_8c){ -_8c.push("",_7b(_7e(_8b)),""); -}; -function _8d(_8e,_8f){ -_8f.push("",_70(_8e),""); -}; -function _79(_90,_91){ -try{ -if(_90===undefined){ -_6f("undefined",_91); -}else{ -if(_90===null){ -_6f("null",_91); -}else{ -if(typeof _90=="string"){ -_86(_90,_91); -}else{ -if(typeof _90=="number"){ -_75(_90,_91); -}else{ -if(typeof _90=="function"){ -_8d(_90,_91); -}else{ -if(_90.nodeType==1){ -_92(_90,_91); -}else{ -if(typeof _90=="object"){ -_93(_90,_91); -}else{ -_6d(_90,_91); -} -} -} -} -} -} -} -} -catch(e){ -} -}; -function _93(_94,_95){ -var _96=_7e(_94); -var _97=/\[object (.*?)\]/; -var m=_97.exec(_96); -_95.push("",m?m[1]:_96,""); -}; -function _92(_98,_99){ -_99.push(""); -_99.push("",_7b(_98.nodeName.toLowerCase()),""); -if(_98.id){ -_99.push("#",_7b(_98.id),""); -} -if(_98.className){ -_99.push(".",_7b(_98.className),""); -} -_99.push(""); -}; -function _29(_9a,_9b){ -if(_9a.nodeType==1){ -_9b.push("
    ","<",_9a.nodeName.toLowerCase(),""); -for(var i=0;i<_9a.attributes.length;++i){ -var _9c=_9a.attributes[i]; -if(!_9c.specified){ -continue; -} -_9b.push(" ",_9c.nodeName.toLowerCase(),"="",_7b(_9c.nodeValue),"""); -} -if(_9a.firstChild){ -_9b.push(">
    "); -for(var _9d=_9a.firstChild;_9d;_9d=_9d.nextSibling){ -_29(_9d,_9b); -} -_9b.push("
    </",_9a.nodeName.toLowerCase(),">
    "); -}else{ -_9b.push("/>"); -} -}else{ -if(_9a.nodeType==3){ -_9b.push("
    ",_7b(_9a.nodeValue),"
    "); -} -} -}; -function _49(_9e,_9f,_a0){ -if(document.all){ -_9e.attachEvent("on"+_9f,_a0); -}else{ -_9e.addEventListener(_9f,_a0,false); -} -}; -function _a1(_a2,_a3,_a4){ -if(document.all){ -_a2.detachEvent("on"+_a3,_a4); -}else{ -_a2.removeEventListener(_a3,_a4,false); -} -}; -function _a5(_a6){ -if(document.all){ -_a6.cancelBubble=true; -}else{ -_a6.stopPropagation(); -} -}; -function _a7(msg,_a8,_a9){ -var _aa=_a8.lastIndexOf("/"); -var _ab=_aa==-1?_a8:_a8.substr(_aa+1); -var _ac=["",msg,"","
    ",_ab," (line ",_a9,")
    "]; -_26(_ac,"error"); -}; -var _ad=new Date().getTime(); -function _4b(_ae){ -var _af=(new Date()).getTime(); -if(_af>_ad+200){ -_ae=dojo.fixEvent(_ae); -var _b0=dojo.keys; -var ekc=_ae.keyCode; -_ad=_af; -if(ekc==_b0.F12){ -_34(); -}else{ -if((ekc==_b0.NUMPAD_ENTER||ekc==76)&&_ae.shiftKey&&(_ae.metaKey||_ae.ctrlKey)){ -_39(); -}else{ -return; -} -} -_a5(_ae); -} -}; -function _4a(e){ -var dk=dojo.keys; -if(e.keyCode==13&&_12.value){ -_b1(_12.value); -_4f(); -}else{ -if(e.keyCode==27){ -_12.value=""; -}else{ -if(e.keyCode==dk.UP_ARROW||e.charCode==dk.UP_ARROW){ -_b2("older"); -}else{ -if(e.keyCode==dk.DOWN_ARROW||e.charCode==dk.DOWN_ARROW){ -_b2("newer"); -}else{ -if(e.keyCode==dk.HOME||e.charCode==dk.HOME){ -_b3=1; -_b2("older"); -}else{ -if(e.keyCode==dk.END||e.charCode==dk.END){ -_b3=999999; -_b2("newer"); -} -} -} -} -} -} -}; -var _b3=-1; -var _b4=null; -function _b1(_b5){ -var _b6=_b7("firebug_history"); -_b6=(_b6)?dojo.fromJson(_b6):[]; -var pos=dojo.indexOf(_b6,_b5); -if(pos!=-1){ -_b6.splice(pos,1); -} -_b6.push(_b5); -_b7("firebug_history",dojo.toJson(_b6),30); -while(_b6.length&&!_b7("firebug_history")){ -_b6.shift(); -_b7("firebug_history",dojo.toJson(_b6),30); -} -_b4=null; -_b3=-1; -}; -function _b2(_b8){ -var _b9=_b7("firebug_history"); -_b9=(_b9)?dojo.fromJson(_b9):[]; -if(!_b9.length){ -return; -} -if(_b4===null){ -_b4=_12.value; -} -if(_b3==-1){ -_b3=_b9.length; -} -if(_b8=="older"){ ---_b3; -if(_b3<0){ -_b3=0; -} -}else{ -if(_b8=="newer"){ -++_b3; -if(_b3>_b9.length){ -_b3=_b9.length; -} -} -} -if(_b3==_b9.length){ -_12.value=_b4; -_b4=null; -}else{ -_12.value=_b9[_b3]; -} -}; -function _b7(_ba,_bb){ -var c=document.cookie; -if(arguments.length==1){ -var _bc=c.match(new RegExp("(?:^|; )"+_ba+"=([^;]*)")); -return _bc?decodeURIComponent(_bc[1]):undefined; -}else{ -var d=new Date(); -d.setMonth(d.getMonth()+1); -document.cookie=_ba+"="+encodeURIComponent(_bb)+((d.toUtcString)?"; expires="+d.toUTCString():""); -} -}; -function _bd(it){ -return it&&it instanceof Array||typeof it=="array"; -}; -function _be(o){ -var cnt=0; -for(var nm in o){ -cnt++; -} -return cnt; -}; -function _25(o,i,txt,_bf){ -var ind=" \t"; -txt=txt||""; -i=i||ind; -_bf=_bf||[]; -var _c0; -if(o&&o.nodeType==1){ -var _c1=[]; -_29(o,_c1); -return _c1.join(""); -} -var br=",\n",cnt=0,_c2=_be(o); -if(o instanceof Date){ -return i+o.toString()+br; -} -looking: -for(var nm in o){ -cnt++; -if(cnt==_c2){ -br="\n"; -} -if(o[nm]===window||o[nm]===document){ -continue; -}else{ -if(o[nm]===null){ -txt+=i+nm+" : NULL"+br; -}else{ -if(o[nm]&&o[nm].nodeType){ -if(o[nm].nodeType==1){ -}else{ -if(o[nm].nodeType==3){ -txt+=i+nm+" : [ TextNode "+o[nm].data+" ]"+br; -} -} -}else{ -if(typeof o[nm]=="object"&&(o[nm] instanceof String||o[nm] instanceof Number||o[nm] instanceof Boolean)){ -txt+=i+nm+" : "+o[nm]+","+br; -}else{ -if(o[nm] instanceof Date){ -txt+=i+nm+" : "+o[nm].toString()+br; -}else{ -if(typeof (o[nm])=="object"&&o[nm]){ -for(var j=0,_c3;_c3=_bf[j];j++){ -if(o[nm]===_c3){ -txt+=i+nm+" : RECURSION"+br; -continue looking; -} -} -_bf.push(o[nm]); -_c0=(_bd(o[nm]))?["[","]"]:["{","}"]; -txt+=i+nm+" : "+_c0[0]+"\n"; -txt+=_25(o[nm],i+ind,"",_bf); -txt+=i+_c0[1]+br; -}else{ -if(typeof o[nm]=="undefined"){ -txt+=i+nm+" : undefined"+br; -}else{ -if(nm=="toString"&&typeof o[nm]=="function"){ -var _c4=o[nm](); -if(typeof _c4=="string"&&_c4.match(/function ?(.*?)\(/)){ -_c4=_7b(_70(o[nm])); -} -txt+=i+nm+" : "+_c4+br; -}else{ -txt+=i+nm+" : "+_7b(_70(o[nm]))+br; -} -} -} -} -} -} -} -} -} -return txt; -}; -function _70(obj){ -var _c5=(obj instanceof Error); -if(obj.nodeType==1){ -return _7b("< "+obj.tagName.toLowerCase()+" id=\""+obj.id+"\" />"); -} -if(obj.nodeType==3){ -return _7b("[TextNode: \""+obj.nodeValue+"\"]"); -} -var nm=(obj&&(obj.id||obj.name||obj.ObjectID||obj.widgetId)); -if(!_c5&&nm){ -return "{"+nm+"}"; -} -var _c6=2; -var _c7=4; -var cnt=0; -if(_c5){ -nm="[ Error: "+(obj.message||obj.description||obj)+" ]"; -}else{ -if(_bd(obj)){ -nm="["+obj.slice(0,_c7).join(","); -if(obj.length>_c7){ -nm+=" ... ("+obj.length+" items)"; -} -nm+="]"; -}else{ -if(typeof obj=="function"){ -nm=obj+""; -var reg=/function\s*([^\(]*)(\([^\)]*\))[^\{]*\{/; -var m=reg.exec(nm); -if(m){ -if(!m[1]){ -m[1]="function"; -} -nm=m[1]+m[2]; -}else{ -nm="function()"; -} -}else{ -if(typeof obj!="object"||typeof obj=="string"){ -nm=obj+""; -}else{ -nm="{"; -for(var i in obj){ -cnt++; -if(cnt>_c6){ -break; -} -nm+=i+":"+_7b(obj[i])+" "; -} -nm+="}"; -} -} -} -} -return nm; -}; -_49(document,dojo.isIE||dojo.isSafari?"keydown":"keypress",_4b); -if((document.documentElement.getAttribute("debug")=="true")||(dojo.config.isDebug)){ -_34(true); -} -dojo.addOnWindowUnload(function(){ -_a1(document,dojo.isIE||dojo.isSafari?"keydown":"keypress",_4b); -window.onFirebugResize=null; -window.console=null; -}); + + var isNewIE = (/Trident/.test(window.navigator.userAgent)); + if(isNewIE){ + // Fixing IE's console + // IE doesn't insert space between arguments. How annoying. + var calls = ["log", "info", "debug", "warn", "error"]; + for(var i=0;i"); + str = str.replace(/\t/g, "    "); + logRow([str], "dir"); + }, + + dirxml: function(node){ + // summary: + // + var html = []; + appendNode(node, html); + logRow(html, "dirxml"); + }, + + group: function(){ + // summary: + // collects log messages into a group, starting with this call and ending with + // groupEnd(). Missing collapse functionality + logRow(arguments, "group", pushGroup); + }, + + groupEnd: function(){ + // summary: + // Closes group. See above + logRow(arguments, "", popGroup); + }, + + time: function(name){ + // summary: + // Starts timers assigned to name given in argument. Timer stops and displays on timeEnd(title); + // example: + // | console.time("load"); + // | console.time("myFunction"); + // | console.timeEnd("load"); + // | console.timeEnd("myFunction"); + timeMap[name] = new Date().getTime(); + }, + + timeEnd: function(name){ + // summary: + // See above. + if(name in timeMap){ + var delta = (new Date()).getTime() - timeMap[name]; + logFormatted([name+ ":", delta+"ms"]); + delete timeMap[name]; + } + }, + + count: function(name){ + // summary: + // Not supported + if(!countMap[name]) countMap[name] = 0; + countMap[name]++; + logFormatted([name+": "+countMap[name]]); + }, + + trace: function(_value){ + var stackAmt = _value || 3; + var f = console.trace.caller; //function that called trace + console.log(">>> console.trace(stack)"); + for(var i=0;i=0&&s.href) { + var h=s.href.replace(/(&|%5C?)forceReload=\d+/,''); + s.href=h+(h.indexOf('?')>=0?'&':'?')+'forceReload='+new Date().valueOf(); + } + } + } + } + + // *************************************************************************** + + function toggleConsole(forceOpen){ + frameVisible = forceOpen || !frameVisible; + if(consoleFrame){ + consoleFrame.style.display = frameVisible ? "block" : "none"; + } + } + + function focusCommandLine(){ + toggleConsole(true); + if(commandLine){ + commandLine.focus(); + } + } + + function openWin(x,y,w,h){ + var win = window.open("","_firebug","status=0,menubar=0,resizable=1,top="+y+",left="+x+",width="+w+",height="+h+",scrollbars=1,addressbar=0"); + if(!win){ + var msg = "Firebug Lite could not open a pop-up window, most likely because of a blocker.\n" + + "Either enable pop-ups for this domain, or change the djConfig to popup=false."; + alert(msg); + } + createResizeHandler(win); + var newDoc=win.document; + //Safari needs an HTML height + var HTMLstring= 'Firebug Lite\n' + + '\n' + + '
    ' + + ''; + + newDoc.write(HTMLstring); + newDoc.close(); + return win; + } + + function createResizeHandler(wn){ + // summary + // Creates handle for onresize window. Called from script in popup's body tag (so that it will work with IE). + // + + var d = new Date(); + d.setTime(d.getTime()+(60*24*60*60*1000)); // 60 days + d = d.toUTCString(); + + var dc = wn.document, + getViewport; + + if (wn.innerWidth){ + getViewport = function(){ + return{w:wn.innerWidth, h:wn.innerHeight}; + }; + }else if (dc.documentElement && dc.documentElement.clientWidth){ + getViewport = function(){ + return{w:dc.documentElement.clientWidth, h:dc.documentElement.clientHeight}; + }; + }else if (dc.body){ + getViewport = function(){ + return{w:dc.body.clientWidth, h:dc.body.clientHeight}; + }; + } + + + window.onFirebugResize = function(){ + + //resize the height of the console log body + layout(getViewport().h); + + clearInterval(wn._firebugWin_resize); + wn._firebugWin_resize = setTimeout(function(){ + var x = wn.screenLeft, + y = wn.screenTop, + w = wn.outerWidth || wn.document.body.offsetWidth, + h = wn.outerHeight || wn.document.body.offsetHeight; + + document.cookie = "_firebugPosition=" + [x,y,w,h].join(",") + "; expires="+d+"; path=/"; + + }, 5000); //can't capture window.onMove - long timeout gives better chance of capturing a resize, then the move + + }; + } + + + /*****************************************************************************/ + + + function createFrame(){ + if(consoleFrame){ + return; + } + + if(dojo.config.popup){ + var containerHeight = "100%"; + var cookieMatch = document.cookie.match(/(?:^|; )_firebugPosition=([^;]*)/); + var p = cookieMatch ? cookieMatch[1].split(",") : [2,2,320,480]; + + _firebugWin = openWin(p[0],p[1],p[2],p[3]); // global + _firebugDoc = _firebugWin.document; // global + + dojo.config.debugContainerId = 'fb'; + + // connecting popup + _firebugWin.console = window.console; + _firebugWin.dojo = window.dojo; + }else{ + _firebugDoc = document; + containerHeight = (dojo.config.debugHeight || 300) + "px"; + } + + var styleElement = _firebugDoc.createElement("link"); + styleElement.href = dojo.moduleUrl("dojo._firebug", "firebug.css"); + styleElement.rel = "stylesheet"; + styleElement.type = "text/css"; + var styleParent = _firebugDoc.getElementsByTagName("head"); + if(styleParent){ + styleParent = styleParent[0]; + } + if(!styleParent){ + styleParent = _firebugDoc.getElementsByTagName("html")[0]; + } + if(dojo.isIE){ + window.setTimeout(function(){ styleParent.appendChild(styleElement); }, 0); + }else{ + styleParent.appendChild(styleElement); + } + + if(dojo.config.debugContainerId){ + consoleFrame = _firebugDoc.getElementById(dojo.config.debugContainerId); + } + if(!consoleFrame){ + consoleFrame = _firebugDoc.createElement("div"); + _firebugDoc.body.appendChild(consoleFrame); + } + consoleFrame.className += " firebug"; + consoleFrame.style.height = containerHeight; + consoleFrame.style.display = (frameVisible ? "block" : "none"); + + var buildLink = function(label, title, method, _class){ + return '
  • '+label+'
  • '; + }; + consoleFrame.innerHTML = + '
    ' + + '
      ' + + + buildLink("Clear", "Remove All Console Logs", "clear", "") + + buildLink("ReCSS", "Refresh CSS without reloading page", "recss", "") + + + buildLink("Console", "Show Console Logs", "openConsole", "gap") + + buildLink("DOM", "Show DOM Inspector", "openDomInspector", "") + + buildLink("Object", "Show Object Inspector", "openObjectInspector", "") + + ((dojo.config.popup) ? "" : buildLink("Close", "Close the console", "close", "gap")) + + + '
    ' + + '
    ' + + '' + + '
    ' + + '' + + ''; + + + consoleToolbar = _firebugDoc.getElementById("firebugToolbar"); + + commandLine = _firebugDoc.getElementById("firebugCommandLine"); + addEvent(commandLine, "keydown", onCommandLineKeyDown); + + addEvent(_firebugDoc, dojo.isIE || dojo.isSafari ? "keydown" : "keypress", onKeyDown); + + consoleBody = _firebugDoc.getElementById("firebugLog"); + consoleObjectInspector = _firebugDoc.getElementById("objectLog"); + consoleDomInspector = _firebugDoc.getElementById("domInspect"); + fireBugTabs = _firebugDoc.getElementById("fireBugTabs"); + layout(); + flush(); + } + + dojo.addOnLoad(createFrame); + + function clearFrame(){ + _firebugDoc = null; + + if(_firebugWin.console){ + _firebugWin.console.clear(); + } + _firebugWin = null; + consoleFrame = null; + consoleBody = null; + consoleObjectInspector = null; + consoleDomInspector = null; + commandLine = null; + messageQueue = []; + groupStack = []; + timeMap = {}; + } + + + function evalCommandLine(){ + var text = commandLine.value; + commandLine.value = ""; + + logRow(["> ", text], "command"); + + var value; + try{ + value = eval(text); + }catch(e){ + console.debug(e); // put exception on the console + } + + console.log(value); + } + + function layout(h){ + var tHeight = 25; //consoleToolbar.offsetHeight; // tab style not ready on load - throws off layout + var height = h ? + h - (tHeight + commandLine.offsetHeight +25 + (h*.01)) + "px" : + (consoleFrame.offsetHeight - tHeight - commandLine.offsetHeight) + "px"; + + consoleBody.style.top = tHeight + "px"; + consoleBody.style.height = height; + consoleObjectInspector.style.height = height; + consoleObjectInspector.style.top = tHeight + "px"; + consoleDomInspector.style.height = height; + consoleDomInspector.style.top = tHeight + "px"; + commandLine.style.bottom = 0; + + dojo.addOnWindowUnload(clearFrame) + } + + function logRow(message, className, handler){ + if(consoleBody){ + writeMessage(message, className, handler); + }else{ + messageQueue.push([message, className, handler]); + } + } + + function flush(){ + var queue = messageQueue; + messageQueue = []; + + for(var i = 0; i < queue.length; ++i){ + writeMessage(queue[i][0], queue[i][1], queue[i][2]); + } + } + + function writeMessage(message, className, handler){ + var isScrolledToBottom = + consoleBody.scrollTop + consoleBody.offsetHeight >= consoleBody.scrollHeight; + + handler = handler||writeRow; + + handler(message, className); + + if(isScrolledToBottom){ + consoleBody.scrollTop = consoleBody.scrollHeight - consoleBody.offsetHeight; + } + } + + function appendRow(row){ + var container = groupStack.length ? groupStack[groupStack.length-1] : consoleBody; + container.appendChild(row); + } + + function writeRow(message, className){ + var row = consoleBody.ownerDocument.createElement("div"); + row.className = "logRow" + (className ? " logRow-"+className : ""); + row.innerHTML = message.join(""); + appendRow(row); + } + + function pushGroup(message, className){ + logFormatted(message, className); + + //var groupRow = consoleBody.ownerDocument.createElement("div"); + //groupRow.className = "logGroup"; + var groupRowBox = consoleBody.ownerDocument.createElement("div"); + groupRowBox.className = "logGroupBox"; + //groupRow.appendChild(groupRowBox); + appendRow(groupRowBox); + groupStack.push(groupRowBox); + } + + function popGroup(){ + groupStack.pop(); + } + + // *************************************************************************** + + function logFormatted(objects, className){ + var html = []; + + var format = objects[0]; + var objIndex = 0; + + if(typeof(format) != "string"){ + format = ""; + objIndex = -1; + } + + var parts = parseFormat(format); + + for(var i = 0; i < parts.length; ++i){ + var part = parts[i]; + if(part && typeof part == "object"){ + part.appender(objects[++objIndex], html); + }else{ + appendText(part, html); + } + } + + + var ids = []; + var obs = []; + for(i = objIndex+1; i < objects.length; ++i){ + appendText(" ", html); + + var object = objects[i]; + if(object === undefined || object === null ){ + appendNull(object, html); + + }else if(typeof(object) == "string"){ + appendText(object, html); + + }else if(object instanceof Date){ + appendText(object.toString(), html); + + }else if(object.nodeType == 9){ + appendText("[ XmlDoc ]", html); + + }else{ + // Create link for object inspector + // need to create an ID for this link, since it is currently text + var id = "_a" + __consoleAnchorId__++; + ids.push(id); + // need to save the object, so the arrays line up + obs.push(object); + var str = ''+getObjectAbbr(object)+''; + + appendLink( str , html); + } + } + + logRow(html, className); + + // Now that the row is inserted in the DOM, loop through all of the links that were just created + for(i=0; i"; + })); + } + } + + function parseFormat(format){ + var parts = []; + + var reg = /((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/; + var appenderMap = {s: appendText, d: appendInteger, i: appendInteger, f: appendFloat}; + + for(var m = reg.exec(format); m; m = reg.exec(format)){ + var type = m[8] ? m[8] : m[5]; + var appender = type in appenderMap ? appenderMap[type] : appendObject; + var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); + + parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); + parts.push({appender: appender, precision: precision}); + + format = format.substr(m.index+m[0].length); + } + + parts.push(format); + + return parts; + } + + function escapeHTML(value){ + function replaceChars(ch){ + switch(ch){ + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + } + return String(value).replace(/[<>&"']/g, replaceChars); + } + + function objectToString(object){ + try{ + return object+""; + }catch(e){ + return null; + } + } + + // *************************************************************************** + function appendLink(object, html){ + // needed for object links - no HTML escaping + html.push( objectToString(object) ); + } + + function appendText(object, html){ + html.push(escapeHTML(objectToString(object))); + } + + function appendNull(object, html){ + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendString(object, html){ + html.push('"', escapeHTML(objectToString(object)), + '"'); + } + + function appendInteger(object, html){ + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendFloat(object, html){ + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendFunction(object, html){ + html.push('', getObjectAbbr(object), ''); + } + + function appendObject(object, html){ + try{ + if(object === undefined){ + appendNull("undefined", html); + }else if(object === null){ + appendNull("null", html); + }else if(typeof object == "string"){ + appendString(object, html); + }else if(typeof object == "number"){ + appendInteger(object, html); + }else if(typeof object == "function"){ + appendFunction(object, html); + }else if(object.nodeType == 1){ + appendSelector(object, html); + }else if(typeof object == "object"){ + appendObjectFormatted(object, html); + }else{ + appendText(object, html); + } + }catch(e){ + /* squelch */ + } + } + + function appendObjectFormatted(object, html){ + var text = objectToString(object); + var reObject = /\[object (.*?)\]/; + + var m = reObject.exec(text); + html.push('', m ? m[1] : text, ''); + } + + function appendSelector(object, html){ + html.push(''); + + html.push('', escapeHTML(object.nodeName.toLowerCase()), ''); + if(object.id){ + html.push('#', escapeHTML(object.id), ''); + } + if(object.className){ + html.push('.', escapeHTML(object.className), ''); + } + + html.push(''); + } + + function appendNode(node, html){ + if(node.nodeType == 1){ + html.push( + '
    ', + '<', node.nodeName.toLowerCase(), ''); + + for(var i = 0; i < node.attributes.length; ++i){ + var attr = node.attributes[i]; + if(!attr.specified){ continue; } + + html.push(' ', attr.nodeName.toLowerCase(), + '="', escapeHTML(attr.nodeValue), + '"'); + } + + if(node.firstChild){ + html.push('>
    '); + + for(var child = node.firstChild; child; child = child.nextSibling){ + appendNode(child, html); + } + + html.push('
    </', + node.nodeName.toLowerCase(), '>
    '); + }else{ + html.push('/>'); + } + }else if (node.nodeType == 3){ + html.push('
    ', escapeHTML(node.nodeValue), + '
    '); + } + } + + // *************************************************************************** + + function addEvent(object, name, handler){ + if(document.all){ + object.attachEvent("on"+name, handler); + }else{ + object.addEventListener(name, handler, false); + } + } + + function removeEvent(object, name, handler){ + if(document.all){ + object.detachEvent("on"+name, handler); + }else{ + object.removeEventListener(name, handler, false); + } + } + + function cancelEvent(event){ + if(document.all){ + event.cancelBubble = true; + }else{ + event.stopPropagation(); + } + } + + function onError(msg, href, lineNo){ + var lastSlash = href.lastIndexOf("/"); + var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1); + + var html = [ + '', msg, '', + '' + ]; + + logRow(html, "error"); + } + + + //After converting to div instead of iframe, now getting two keydowns right away in IE 6. + //Make sure there is a little bit of delay. + var onKeyDownTime = new Date().getTime(); + + function onKeyDown(event){ + var timestamp = (new Date()).getTime(); + if(timestamp > onKeyDownTime + 200){ + event = dojo.fixEvent(event); + var keys = dojo.keys; + var ekc = event.keyCode; + onKeyDownTime = timestamp; + if(ekc == keys.F12){ + toggleConsole(); + }else if( + (ekc == keys.NUMPAD_ENTER || ekc == 76) && + event.shiftKey && + (event.metaKey || event.ctrlKey) + ){ + focusCommandLine(); + }else{ + return; + } + cancelEvent(event); + } + } + + function onCommandLineKeyDown(e){ + var dk = dojo.keys; + if(e.keyCode == 13 && commandLine.value){ + addToHistory(commandLine.value); + evalCommandLine(); + }else if(e.keyCode == 27){ + commandLine.value = ""; + }else if(e.keyCode == dk.UP_ARROW || e.charCode == dk.UP_ARROW){ + navigateHistory("older"); + }else if(e.keyCode == dk.DOWN_ARROW || e.charCode == dk.DOWN_ARROW){ + navigateHistory("newer"); + }else if(e.keyCode == dk.HOME || e.charCode == dk.HOME){ + historyPosition = 1; + navigateHistory("older"); + }else if(e.keyCode == dk.END || e.charCode == dk.END){ + historyPosition = 999999; + navigateHistory("newer"); + } + } + + var historyPosition = -1; + var historyCommandLine = null; + + function addToHistory(value){ + var history = cookie("firebug_history"); + history = (history) ? dojo.fromJson(history) : []; + var pos = dojo.indexOf(history, value); + if (pos != -1){ + history.splice(pos, 1); + } + history.push(value); + cookie("firebug_history", dojo.toJson(history), 30); + while(history.length && !cookie("firebug_history")){ + history.shift(); + cookie("firebug_history", dojo.toJson(history), 30); + } + historyCommandLine = null; + historyPosition = -1; + } + + function navigateHistory(direction){ + var history = cookie("firebug_history"); + history = (history) ? dojo.fromJson(history) : []; + if(!history.length){ + return; + } + + if(historyCommandLine === null){ + historyCommandLine = commandLine.value; + } + + if(historyPosition == -1){ + historyPosition = history.length; + } + + if(direction == "older"){ + --historyPosition; + if(historyPosition < 0){ + historyPosition = 0; + } + }else if(direction == "newer"){ + ++historyPosition; + if(historyPosition > history.length){ + historyPosition = history.length; + } + } + + if(historyPosition == history.length){ + commandLine.value = historyCommandLine; + historyCommandLine = null; + }else{ + commandLine.value = history[historyPosition]; + } + } + + function cookie(name, value){ + var c = document.cookie; + if(arguments.length == 1){ + var matches = c.match(new RegExp("(?:^|; )" + name + "=([^;]*)")); + return matches ? decodeURIComponent(matches[1]) : undefined; // String or undefined + }else{ + var d = new Date(); + d.setMonth(d.getMonth()+1); + document.cookie = name + "=" + encodeURIComponent(value) + ((d.toUtcString) ? "; expires=" + d.toUTCString() : ""); + } + }; + + function isArray(it){ + return it && it instanceof Array || typeof it == "array"; + } + + //*************************************************************************************************** + // Print Object Helpers + function objectLength(o){ + var cnt = 0; + for(var nm in o){ + cnt++ + } + return cnt; + } + + function printObject(o, i, txt, used){ + // Recursively trace object, indenting to represent depth for display in object inspector + var ind = " \t"; + txt = txt || ""; + i = i || ind; + used = used || []; + var opnCls; + + if(o && o.nodeType == 1){ + var html = []; + appendNode(o, html); + return html.join(""); + } + + var br=",\n", cnt = 0, length = objectLength(o); + + if(o instanceof Date){ + return i + o.toString() + br; + } + looking: + for(var nm in o){ + cnt++; + if(cnt==length){br = "\n";} + if(o[nm] === window || o[nm] === document){ + continue; + }else if(o[nm] === null){ + txt += i+nm + " : NULL" + br; + }else if(o[nm] && o[nm].nodeType){ + if(o[nm].nodeType == 1){ + //txt += i+nm + " : < "+o[nm].tagName+" id=\""+ o[nm].id+"\" />" + br; + }else if(o[nm].nodeType == 3){ + txt += i+nm + " : [ TextNode "+o[nm].data + " ]" + br; + } + + }else if(typeof o[nm] == "object" && (o[nm] instanceof String || o[nm] instanceof Number || o[nm] instanceof Boolean)){ + txt += i+nm + " : " + o[nm] + "," + br; + + }else if(o[nm] instanceof Date){ + txt += i+nm + " : " + o[nm].toString() + br; + + }else if(typeof(o[nm]) == "object" && o[nm]){ + for(var j = 0, seen; seen = used[j]; j++){ + if(o[nm] === seen){ + txt += i+nm + " : RECURSION" + br; + continue looking; + } + } + used.push(o[nm]); + + opnCls = (isArray(o[nm]))?["[","]"]:["{","}"]; + txt += i+nm +" : " + opnCls[0] + "\n";//non-standard break, (no comma) + txt += printObject(o[nm], i+ind, "", used); + txt += i + opnCls[1] + br; + + }else if(typeof o[nm] == "undefined"){ + txt += i+nm + " : undefined" + br; + }else if(nm == "toString" && typeof o[nm] == "function"){ + var toString = o[nm](); + if(typeof toString == "string" && toString.match(/function ?(.*?)\(/)){ + toString = escapeHTML(getObjectAbbr(o[nm])); + } + txt += i+nm +" : " + toString + br; + }else{ + txt += i+nm +" : "+ escapeHTML(getObjectAbbr(o[nm])) + br; + } + } + return txt; + } + + function getObjectAbbr(obj){ + // Gets an abbreviation of an object for display in log + // X items in object, including id + // X items in an array + // TODO: Firebug Sr. actually goes by char count + var isError = (obj instanceof Error); + if(obj.nodeType == 1){ + return escapeHTML('< '+obj.tagName.toLowerCase()+' id=\"'+ obj.id+ '\" />'); + } + if(obj.nodeType == 3){ + return escapeHTML('[TextNode: "'+obj.nodeValue+'"]'); + } + var nm = (obj && (obj.id || obj.name || obj.ObjectID || obj.widgetId)); + if(!isError && nm){ return "{"+nm+"}"; } + + var obCnt = 2; + var arCnt = 4; + var cnt = 0; + + if(isError){ + nm = "[ Error: "+(obj.message || obj.description || obj)+" ]"; + }else if(isArray(obj)){ + nm = "[" + obj.slice(0,arCnt).join(","); + if(obj.length > arCnt){ + nm += " ... ("+obj.length+" items)"; + } + nm += "]"; + }else if(typeof obj == "function"){ + nm = obj + ""; + var reg = /function\s*([^\(]*)(\([^\)]*\))[^\{]*\{/; + var m = reg.exec(nm); + if(m){ + if(!m[1]){ + m[1] = "function"; + } + nm = m[1] + m[2]; + }else{ + nm = "function()"; + } + }else if(typeof obj != "object" || typeof obj == "string"){ + nm = obj + ""; + }else{ + nm = "{"; + for(var i in obj){ + cnt++; + if(cnt > obCnt){ break; } + nm += i+":"+escapeHTML(obj[i])+" "; + } + nm+="}"; + } + + return nm; + } + + //************************************************************************************* + + //window.onerror = onError; + + addEvent(document, dojo.isIE || dojo.isSafari ? "keydown" : "keypress", onKeyDown); + + if( (document.documentElement.getAttribute("debug") == "true")|| + (dojo.config.isDebug) + ){ + toggleConsole(true); + } + + dojo.addOnWindowUnload(function(){ + // Erase the globals and event handlers I created, to prevent spurious leak warnings + removeEvent(document, dojo.isIE || dojo.isSafari ? "keydown" : "keypress", onKeyDown); + window.onFirebugResize = null; + window.console = null; + }); } + })(); + + } diff --git a/lib/dojo/back.js b/lib/dojo/back.js index a2656287..5e2998c9 100644 --- a/lib/dojo/back.js +++ b/lib/dojo/back.js @@ -5,254 +5,406 @@ */ -if(!dojo._hasResource["dojo.back"]){ -dojo._hasResource["dojo.back"]=true; +if(!dojo._hasResource["dojo.back"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.back"] = true; dojo.provide("dojo.back"); -(function(){ -var _1=dojo.back; -function _2(){ -var h=window.location.hash; -if(h.charAt(0)=="#"){ -h=h.substring(1); -} -return dojo.isMozilla?h:decodeURIComponent(h); -}; -function _3(h){ -if(!h){ -h=""; -} -window.location.hash=encodeURIComponent(h); -_4=history.length; -}; -if(dojo.exists("tests.back-hash")){ -_1.getHash=_2; -_1.setHash=_3; -} -var _5=(typeof (window)!=="undefined")?window.location.href:""; -var _6=(typeof (window)!=="undefined")?_2():""; -var _7=null; -var _8=null; -var _9=null; -var _a=null; -var _b=[]; -var _c=[]; -var _d=false; -var _e=false; -var _4; -function _f(){ -var _10=_c.pop(); -if(!_10){ -return; -} -var _11=_c[_c.length-1]; -if(!_11&&_c.length==0){ -_11=_7; -} -if(_11){ -if(_11.kwArgs["back"]){ -_11.kwArgs["back"](); -}else{ -if(_11.kwArgs["backButton"]){ -_11.kwArgs["backButton"](); -}else{ -if(_11.kwArgs["handle"]){ -_11.kwArgs.handle("back"); -} -} -} -} -_b.push(_10); -}; -_1.goBack=_f; -function _12(){ -var _13=_b.pop(); -if(!_13){ -return; -} -if(_13.kwArgs["forward"]){ -_13.kwArgs.forward(); -}else{ -if(_13.kwArgs["forwardButton"]){ -_13.kwArgs.forwardButton(); -}else{ -if(_13.kwArgs["handle"]){ -_13.kwArgs.handle("forward"); -} -} -} -_c.push(_13); -}; -_1.goForward=_12; -function _14(url,_15,_16){ -return {"url":url,"kwArgs":_15,"urlHash":_16}; -}; -function _17(url){ -var _18=url.split("?"); -if(_18.length<2){ -return null; -}else{ -return _18[1]; -} -}; -function _19(){ -var url=(dojo.config["dojoIframeHistoryUrl"]||dojo.moduleUrl("dojo","resources/iframe_history.html"))+"?"+(new Date()).getTime(); -_d=true; -if(_a){ -dojo.isWebKit?_a.location=url:window.frames[_a.name].location=url; -}else{ -} -return url; -}; -function _1a(){ -if(!_e){ -var hsl=_c.length; -var _1b=_2(); -if((_1b===_6||window.location.href==_5)&&(hsl==1)){ -_f(); -return; -} -if(_b.length>0){ -if(_b[_b.length-1].urlHash===_1b){ -_12(); -return; -} -} -if((hsl>=2)&&(_c[hsl-2])){ -if(_c[hsl-2].urlHash===_1b){ -_f(); -return; -} -} -if(dojo.isSafari&&dojo.isSafari<3){ -var _1c=history.length; -if(_1c>_4){ -_12(); -}else{ -if(_1c<_4){ -_f(); -} -} -_4=_1c; -} -} -}; -_1.init=function(){ -if(dojo.byId("dj_history")){ -return; -} -var src=dojo.config["dojoIframeHistoryUrl"]||dojo.moduleUrl("dojo","resources/iframe_history.html"); -if(dojo._postLoad){ -console.error("dojo.back.init() must be called before the DOM has loaded. "+"If using xdomain loading or djConfig.debugAtAllCosts, include dojo.back "+"in a build layer."); -}else{ -document.write(""); -} -}; -_1.setInitialState=function(_1d){ -_7=_14(_5,_1d,_6); -}; -_1.addToHistory=function(_1e){ -_b=[]; -var _1f=null; -var url=null; -if(!_a){ -if(dojo.config["useXDomain"]&&!dojo.config["dojoIframeHistoryUrl"]){ -console.warn("dojo.back: When using cross-domain Dojo builds,"+" please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl"+" to the path on your domain to iframe_history.html"); -} -_a=window.frames["dj_history"]; -} -if(!_9){ -_9=dojo.create("a",{style:{display:"none"}},dojo.body()); -} -if(_1e["changeUrl"]){ -_1f=""+((_1e["changeUrl"]!==true)?_1e["changeUrl"]:(new Date()).getTime()); -if(_c.length==0&&_7.urlHash==_1f){ -_7=_14(url,_1e,_1f); -return; -}else{ -if(_c.length>0&&_c[_c.length-1].urlHash==_1f){ -_c[_c.length-1]=_14(url,_1e,_1f); -return; -} -} -_e=true; -setTimeout(function(){ -_3(_1f); -_e=false; -},1); -_9.href=_1f; -if(dojo.isIE){ -url=_19(); -var _20=_1e["back"]||_1e["backButton"]||_1e["handle"]; -var tcb=function(_21){ -if(_2()!=""){ -setTimeout(function(){ -_3(_1f); -},1); -} -_20.apply(this,[_21]); -}; -if(_1e["back"]){ -_1e.back=tcb; -}else{ -if(_1e["backButton"]){ -_1e.backButton=tcb; -}else{ -if(_1e["handle"]){ -_1e.handle=tcb; -} -} -} -var _22=_1e["forward"]||_1e["forwardButton"]||_1e["handle"]; -var tfw=function(_23){ -if(_2()!=""){ -_3(_1f); -} -if(_22){ -_22.apply(this,[_23]); -} -}; -if(_1e["forward"]){ -_1e.forward=tfw; -}else{ -if(_1e["forwardButton"]){ -_1e.forwardButton=tfw; -}else{ -if(_1e["handle"]){ -_1e.handle=tfw; -} -} -} -}else{ -if(!dojo.isIE){ -if(!_8){ -_8=setInterval(_1a,200); -} -} -} -}else{ -url=_19(); -} -_c.push(_14(url,_1e,_1f)); -}; -_1._iframeLoaded=function(evt,_24){ -var _25=_17(_24.href); -if(_25==null){ -if(_c.length==1){ -_f(); -} -return; -} -if(_d){ -_d=false; -return; -} -if(_c.length>=2&&_25==_17(_c[_c.length-2].url)){ -_f(); -}else{ -if(_b.length>0&&_25==_17(_b[_b.length-1].url)){ -_12(); -} + +/*===== +dojo.back = { + // summary: Browser history management resources } -}; -})(); +=====*/ + + +(function(){ + var back = dojo.back; + + // everyone deals with encoding the hash slightly differently + + function getHash(){ + var h = window.location.hash; + if(h.charAt(0) == "#"){ h = h.substring(1); } + return dojo.isMozilla ? h : decodeURIComponent(h); + } + + function setHash(h){ + if(!h){ h = ""; } + window.location.hash = encodeURIComponent(h); + historyCounter = history.length; + } + + // if we're in the test for these methods, expose them on dojo.back. ok'd with alex. + if(dojo.exists("tests.back-hash")){ + back.getHash = getHash; + back.setHash = setHash; + } + + var initialHref = (typeof(window) !== "undefined") ? window.location.href : ""; + var initialHash = (typeof(window) !== "undefined") ? getHash() : ""; + var initialState = null; + + var locationTimer = null; + var bookmarkAnchor = null; + var historyIframe = null; + var forwardStack = []; + var historyStack = []; + var moveForward = false; + var changingUrl = false; + var historyCounter; + + function handleBackButton(){ + //summary: private method. Do not call this directly. + + //The "current" page is always at the top of the history stack. + var current = historyStack.pop(); + if(!current){ return; } + var last = historyStack[historyStack.length-1]; + if(!last && historyStack.length == 0){ + last = initialState; + } + if(last){ + if(last.kwArgs["back"]){ + last.kwArgs["back"](); + }else if(last.kwArgs["backButton"]){ + last.kwArgs["backButton"](); + }else if(last.kwArgs["handle"]){ + last.kwArgs.handle("back"); + } + } + forwardStack.push(current); + } + + back.goBack = handleBackButton; + + function handleForwardButton(){ + //summary: private method. Do not call this directly. + var last = forwardStack.pop(); + if(!last){ return; } + if(last.kwArgs["forward"]){ + last.kwArgs.forward(); + }else if(last.kwArgs["forwardButton"]){ + last.kwArgs.forwardButton(); + }else if(last.kwArgs["handle"]){ + last.kwArgs.handle("forward"); + } + historyStack.push(last); + } + + back.goForward = handleForwardButton; + + function createState(url, args, hash){ + //summary: private method. Do not call this directly. + return {"url": url, "kwArgs": args, "urlHash": hash}; //Object + } + + function getUrlQuery(url){ + //summary: private method. Do not call this directly. + var segments = url.split("?"); + if(segments.length < 2){ + return null; //null + } + else{ + return segments[1]; //String + } + } + + function loadIframeHistory(){ + //summary: private method. Do not call this directly. + var url = (dojo.config["dojoIframeHistoryUrl"] || dojo.moduleUrl("dojo", "resources/iframe_history.html")) + "?" + (new Date()).getTime(); + moveForward = true; + if(historyIframe){ + dojo.isWebKit ? historyIframe.location = url : window.frames[historyIframe.name].location = url; + }else{ + //console.warn("dojo.back: Not initialised. You need to call dojo.back.init() from a + // +}; +=====*/ + +// All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require) + + +// And some other stuff that we tend to pull in all the time anyway + + + + + + + +} + +if(!dojo._hasResource["dojo.fx.Toggler"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.fx.Toggler"] = true; +dojo.provide("dojo.fx.Toggler"); + +dojo.declare("dojo.fx.Toggler", null, { + // summary: + // A simple `dojo.Animation` toggler API. + // + // description: + // class constructor for an animation toggler. It accepts a packed + // set of arguments about what type of animation to use in each + // direction, duration, etc. All available members are mixed into + // these animations from the constructor (for example, `node`, + // `showDuration`, `hideDuration`). + // + // example: + // | var t = new dojo.fx.Toggler({ + // | node: "nodeId", + // | showDuration: 500, + // | // hideDuration will default to "200" + // | showFunc: dojo.fx.wipeIn, + // | // hideFunc will default to "fadeOut" + // | }); + // | t.show(100); // delay showing for 100ms + // | // ...time passes... + // | t.hide(); + + // node: DomNode + // the node to target for the showing and hiding animations + node: null, + + // showFunc: Function + // The function that returns the `dojo.Animation` to show the node + showFunc: dojo.fadeIn, + + // hideFunc: Function + // The function that returns the `dojo.Animation` to hide the node + hideFunc: dojo.fadeOut, + + // showDuration: + // Time in milliseconds to run the show Animation + showDuration: 200, + + // hideDuration: + // Time in milliseconds to run the hide Animation + hideDuration: 200, + + // FIXME: need a policy for where the toggler should "be" the next + // time show/hide are called if we're stopped somewhere in the + // middle. + // FIXME: also would be nice to specify individual showArgs/hideArgs mixed into + // each animation individually. + // FIXME: also would be nice to have events from the animations exposed/bridged + + /*===== + _showArgs: null, + _showAnim: null, + + _hideArgs: null, + _hideAnim: null, + + _isShowing: false, + _isHiding: false, + =====*/ + + constructor: function(args){ + var _t = this; + + dojo.mixin(_t, args); + _t.node = args.node; + _t._showArgs = dojo.mixin({}, args); + _t._showArgs.node = _t.node; + _t._showArgs.duration = _t.showDuration; + _t.showAnim = _t.showFunc(_t._showArgs); + + _t._hideArgs = dojo.mixin({}, args); + _t._hideArgs.node = _t.node; + _t._hideArgs.duration = _t.hideDuration; + _t.hideAnim = _t.hideFunc(_t._hideArgs); + + dojo.connect(_t.showAnim, "beforeBegin", dojo.hitch(_t.hideAnim, "stop", true)); + dojo.connect(_t.hideAnim, "beforeBegin", dojo.hitch(_t.showAnim, "stop", true)); + }, + + show: function(delay){ + // summary: Toggle the node to showing + // delay: Integer? + // Ammount of time to stall playing the show animation + return this.showAnim.play(delay || 0); + }, + + hide: function(delay){ + // summary: Toggle the node to hidden + // delay: Integer? + // Ammount of time to stall playing the hide animation + return this.hideAnim.play(delay || 0); + } +}); + +} + +if(!dojo._hasResource["dojo.fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.fx"] = true; +dojo.provide("dojo.fx"); + // FIXME: remove this back-compat require in 2.0 +/*===== +dojo.fx = { + // summary: Effects library on top of Base animations +}; +=====*/ +(function(){ + + var d = dojo, + _baseObj = { + _fire: function(evt, args){ + if(this[evt]){ + this[evt].apply(this, args||[]); + } + return this; + } + }; + + var _chain = function(animations){ + this._index = -1; + this._animations = animations||[]; + this._current = this._onAnimateCtx = this._onEndCtx = null; + + this.duration = 0; + d.forEach(this._animations, function(a){ + this.duration += a.duration; + if(a.delay){ this.duration += a.delay; } + }, this); + }; + d.extend(_chain, { + _onAnimate: function(){ + this._fire("onAnimate", arguments); + }, + _onEnd: function(){ + d.disconnect(this._onAnimateCtx); + d.disconnect(this._onEndCtx); + this._onAnimateCtx = this._onEndCtx = null; + if(this._index + 1 == this._animations.length){ + this._fire("onEnd"); + }else{ + // switch animations + this._current = this._animations[++this._index]; + this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate"); + this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd"); + this._current.play(0, true); + } + }, + play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ + if(!this._current){ this._current = this._animations[this._index = 0]; } + if(!gotoStart && this._current.status() == "playing"){ return this; } + var beforeBegin = d.connect(this._current, "beforeBegin", this, function(){ + this._fire("beforeBegin"); + }), + onBegin = d.connect(this._current, "onBegin", this, function(arg){ + this._fire("onBegin", arguments); + }), + onPlay = d.connect(this._current, "onPlay", this, function(arg){ + this._fire("onPlay", arguments); + d.disconnect(beforeBegin); + d.disconnect(onBegin); + d.disconnect(onPlay); + }); + if(this._onAnimateCtx){ + d.disconnect(this._onAnimateCtx); + } + this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate"); + if(this._onEndCtx){ + d.disconnect(this._onEndCtx); + } + this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd"); + this._current.play.apply(this._current, arguments); + return this; + }, + pause: function(){ + if(this._current){ + var e = d.connect(this._current, "onPause", this, function(arg){ + this._fire("onPause", arguments); + d.disconnect(e); + }); + this._current.pause(); + } + return this; + }, + gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){ + this.pause(); + var offset = this.duration * percent; + this._current = null; + d.some(this._animations, function(a){ + if(a.duration <= offset){ + this._current = a; + return true; + } + offset -= a.duration; + return false; + }); + if(this._current){ + this._current.gotoPercent(offset / this._current.duration, andPlay); + } + return this; + }, + stop: function(/*boolean?*/ gotoEnd){ + if(this._current){ + if(gotoEnd){ + for(; this._index + 1 < this._animations.length; ++this._index){ + this._animations[this._index].stop(true); + } + this._current = this._animations[this._index]; + } + var e = d.connect(this._current, "onStop", this, function(arg){ + this._fire("onStop", arguments); + d.disconnect(e); + }); + this._current.stop(); + } + return this; + }, + status: function(){ + return this._current ? this._current.status() : "stopped"; + }, + destroy: function(){ + if(this._onAnimateCtx){ d.disconnect(this._onAnimateCtx); } + if(this._onEndCtx){ d.disconnect(this._onEndCtx); } + } + }); + d.extend(_chain, _baseObj); + + dojo.fx.chain = function(/*dojo.Animation[]*/ animations){ + // summary: + // Chain a list of `dojo.Animation`s to run in sequence + // + // description: + // Return a `dojo.Animation` which will play all passed + // `dojo.Animation` instances in sequence, firing its own + // synthesized events simulating a single animation. (eg: + // onEnd of this animation means the end of the chain, + // not the individual animations within) + // + // example: + // Once `node` is faded out, fade in `otherNode` + // | dojo.fx.chain([ + // | dojo.fadeIn({ node:node }), + // | dojo.fadeOut({ node:otherNode }) + // | ]).play(); + // + return new _chain(animations) // dojo.Animation + }; + + var _combine = function(animations){ + this._animations = animations||[]; + this._connects = []; + this._finished = 0; + + this.duration = 0; + d.forEach(animations, function(a){ + var duration = a.duration; + if(a.delay){ duration += a.delay; } + if(this.duration < duration){ this.duration = duration; } + this._connects.push(d.connect(a, "onEnd", this, "_onEnd")); + }, this); + + this._pseudoAnimation = new d.Animation({curve: [0, 1], duration: this.duration}); + var self = this; + d.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"], + function(evt){ + self._connects.push(d.connect(self._pseudoAnimation, evt, + function(){ self._fire(evt, arguments); } + )); + } + ); + }; + d.extend(_combine, { + _doAction: function(action, args){ + d.forEach(this._animations, function(a){ + a[action].apply(a, args); + }); + return this; + }, + _onEnd: function(){ + if(++this._finished > this._animations.length){ + this._fire("onEnd"); + } + }, + _call: function(action, args){ + var t = this._pseudoAnimation; + t[action].apply(t, args); + }, + play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ + this._finished = 0; + this._doAction("play", arguments); + this._call("play", arguments); + return this; + }, + pause: function(){ + this._doAction("pause", arguments); + this._call("pause", arguments); + return this; + }, + gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){ + var ms = this.duration * percent; + d.forEach(this._animations, function(a){ + a.gotoPercent(a.duration < ms ? 1 : (ms / a.duration), andPlay); + }); + this._call("gotoPercent", arguments); + return this; + }, + stop: function(/*boolean?*/ gotoEnd){ + this._doAction("stop", arguments); + this._call("stop", arguments); + return this; + }, + status: function(){ + return this._pseudoAnimation.status(); + }, + destroy: function(){ + d.forEach(this._connects, dojo.disconnect); + } + }); + d.extend(_combine, _baseObj); + + dojo.fx.combine = function(/*dojo.Animation[]*/ animations){ + // summary: + // Combine a list of `dojo.Animation`s to run in parallel + // + // description: + // Combine an array of `dojo.Animation`s to run in parallel, + // providing a new `dojo.Animation` instance encompasing each + // animation, firing standard animation events. + // + // example: + // Fade out `node` while fading in `otherNode` simultaneously + // | dojo.fx.combine([ + // | dojo.fadeIn({ node:node }), + // | dojo.fadeOut({ node:otherNode }) + // | ]).play(); + // + // example: + // When the longest animation ends, execute a function: + // | var anim = dojo.fx.combine([ + // | dojo.fadeIn({ node: n, duration:700 }), + // | dojo.fadeOut({ node: otherNode, duration: 300 }) + // | ]); + // | dojo.connect(anim, "onEnd", function(){ + // | // overall animation is done. + // | }); + // | anim.play(); // play the animation + // + return new _combine(animations); // dojo.Animation + }; + + dojo.fx.wipeIn = function(/*Object*/ args){ + // summary: + // Expand a node to it's natural height. + // + // description: + // Returns an animation that will expand the + // node defined in 'args' object from it's current height to + // it's natural height (with no scrollbar). + // Node must have no margin/border/padding. + // + // args: Object + // A hash-map of standard `dojo.Animation` constructor properties + // (such as easing: node: duration: and so on) + // + // example: + // | dojo.fx.wipeIn({ + // | node:"someId" + // | }).play() + var node = args.node = d.byId(args.node), s = node.style, o; + + var anim = d.animateProperty(d.mixin({ + properties: { + height: { + // wrapped in functions so we wait till the last second to query (in case value has changed) + start: function(){ + // start at current [computed] height, but use 1px rather than 0 + // because 0 causes IE to display the whole panel + o = s.overflow; + s.overflow = "hidden"; + if(s.visibility == "hidden" || s.display == "none"){ + s.height = "1px"; + s.display = ""; + s.visibility = ""; + return 1; + }else{ + var height = d.style(node, "height"); + return Math.max(height, 1); + } + }, + end: function(){ + return node.scrollHeight; + } + } + } + }, args)); + + d.connect(anim, "onEnd", function(){ + s.height = "auto"; + s.overflow = o; + }); + + return anim; // dojo.Animation + } + + dojo.fx.wipeOut = function(/*Object*/ args){ + // summary: + // Shrink a node to nothing and hide it. + // + // description: + // Returns an animation that will shrink node defined in "args" + // from it's current height to 1px, and then hide it. + // + // args: Object + // A hash-map of standard `dojo.Animation` constructor properties + // (such as easing: node: duration: and so on) + // + // example: + // | dojo.fx.wipeOut({ node:"someId" }).play() + + var node = args.node = d.byId(args.node), s = node.style, o; + + var anim = d.animateProperty(d.mixin({ + properties: { + height: { + end: 1 // 0 causes IE to display the whole panel + } + } + }, args)); + + d.connect(anim, "beforeBegin", function(){ + o = s.overflow; + s.overflow = "hidden"; + s.display = ""; + }); + d.connect(anim, "onEnd", function(){ + s.overflow = o; + s.height = "auto"; + s.display = "none"; + }); + + return anim; // dojo.Animation + } + + dojo.fx.slideTo = function(/*Object*/ args){ + // summary: + // Slide a node to a new top/left position + // + // description: + // Returns an animation that will slide "node" + // defined in args Object from its current position to + // the position defined by (args.left, args.top). + // + // args: Object + // A hash-map of standard `dojo.Animation` constructor properties + // (such as easing: node: duration: and so on). Special args members + // are `top` and `left`, which indicate the new position to slide to. + // + // example: + // | dojo.fx.slideTo({ node: node, left:"40", top:"50", units:"px" }).play() + + var node = args.node = d.byId(args.node), + top = null, left = null; + + var init = (function(n){ + return function(){ + var cs = d.getComputedStyle(n); + var pos = cs.position; + top = (pos == 'absolute' ? n.offsetTop : parseInt(cs.top) || 0); + left = (pos == 'absolute' ? n.offsetLeft : parseInt(cs.left) || 0); + if(pos != 'absolute' && pos != 'relative'){ + var ret = d.position(n, true); + top = ret.y; + left = ret.x; + n.style.position="absolute"; + n.style.top=top+"px"; + n.style.left=left+"px"; + } + }; + })(node); + init(); + + var anim = d.animateProperty(d.mixin({ + properties: { + top: args.top || 0, + left: args.left || 0 + } + }, args)); + d.connect(anim, "beforeBegin", anim, init); + + return anim; // dojo.Animation + } + +})(); + +} + +if(!dojo._hasResource["dojo.NodeList-fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.NodeList-fx"] = true; +dojo.provide("dojo.NodeList-fx"); + + +/*===== +dojo["NodeList-fx"] = { + // summary: Adds dojo.fx animation support to dojo.query() +}; +=====*/ + +dojo.extend(dojo.NodeList, { + _anim: function(obj, method, args){ + args = args||{}; + var a = dojo.fx.combine( + this.map(function(item){ + var tmpArgs = { node: item }; + dojo.mixin(tmpArgs, args); + return obj[method](tmpArgs); + }) + ); + return args.auto ? a.play() && this : a; // dojo.Animation|dojo.NodeList + }, + + wipeIn: function(args){ + // summary: + // wipe in all elements of this NodeList via `dojo.fx.wipeIn` + // + // args: Object? + // Additional dojo.Animation arguments to mix into this set with the addition of + // an `auto` parameter. + // + // returns: dojo.Animation|dojo.NodeList + // A special args member `auto` can be passed to automatically play the animation. + // If args.auto is present, the original dojo.NodeList will be returned for further + // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed + // + // example: + // Fade in all tables with class "blah": + // | dojo.query("table.blah").wipeIn().play(); + // + // example: + // Utilizing `auto` to get the NodeList back: + // | dojo.query(".titles").wipeIn({ auto:true }).onclick(someFunction); + // + return this._anim(dojo.fx, "wipeIn", args); // dojo.Animation|dojo.NodeList + }, + + wipeOut: function(args){ + // summary: + // wipe out all elements of this NodeList via `dojo.fx.wipeOut` + // + // args: Object? + // Additional dojo.Animation arguments to mix into this set with the addition of + // an `auto` parameter. + // + // returns: dojo.Animation|dojo.NodeList + // A special args member `auto` can be passed to automatically play the animation. + // If args.auto is present, the original dojo.NodeList will be returned for further + // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed + // + // example: + // Wipe out all tables with class "blah": + // | dojo.query("table.blah").wipeOut().play(); + return this._anim(dojo.fx, "wipeOut", args); // dojo.Animation|dojo.NodeList + }, + + slideTo: function(args){ + // summary: + // slide all elements of the node list to the specified place via `dojo.fx.slideTo` + // + // args: Object? + // Additional dojo.Animation arguments to mix into this set with the addition of + // an `auto` parameter. + // + // returns: dojo.Animation|dojo.NodeList + // A special args member `auto` can be passed to automatically play the animation. + // If args.auto is present, the original dojo.NodeList will be returned for further + // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed + // + // example: + // | Move all tables with class "blah" to 300/300: + // | dojo.query("table.blah").slideTo({ + // | left: 40, + // | top: 50 + // | }).play(); + return this._anim(dojo.fx, "slideTo", args); // dojo.Animation|dojo.NodeList + }, + + + fadeIn: function(args){ + // summary: + // fade in all elements of this NodeList via `dojo.fadeIn` + // + // args: Object? + // Additional dojo.Animation arguments to mix into this set with the addition of + // an `auto` parameter. + // + // returns: dojo.Animation|dojo.NodeList + // A special args member `auto` can be passed to automatically play the animation. + // If args.auto is present, the original dojo.NodeList will be returned for further + // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed + // + // example: + // Fade in all tables with class "blah": + // | dojo.query("table.blah").fadeIn().play(); + return this._anim(dojo, "fadeIn", args); // dojo.Animation|dojo.NodeList + }, + + fadeOut: function(args){ + // summary: + // fade out all elements of this NodeList via `dojo.fadeOut` + // + // args: Object? + // Additional dojo.Animation arguments to mix into this set with the addition of + // an `auto` parameter. + // + // returns: dojo.Animation|dojo.NodeList + // A special args member `auto` can be passed to automatically play the animation. + // If args.auto is present, the original dojo.NodeList will be returned for further + // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed + // + // example: + // Fade out all elements with class "zork": + // | dojo.query(".zork").fadeOut().play(); + // example: + // Fade them on a delay and do something at the end: + // | var fo = dojo.query(".zork").fadeOut(); + // | dojo.connect(fo, "onEnd", function(){ /*...*/ }); + // | fo.play(); + // example: + // Using `auto`: + // | dojo.query("li").fadeOut({ auto:true }).filter(filterFn).forEach(doit); + // + return this._anim(dojo, "fadeOut", args); // dojo.Animation|dojo.NodeList + }, + + animateProperty: function(args){ + // summary: + // Animate all elements of this NodeList across the properties specified. + // syntax identical to `dojo.animateProperty` + // + // returns: dojo.Animation|dojo.NodeList + // A special args member `auto` can be passed to automatically play the animation. + // If args.auto is present, the original dojo.NodeList will be returned for further + // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed + // + // example: + // | dojo.query(".zork").animateProperty({ + // | duration: 500, + // | properties: { + // | color: { start: "black", end: "white" }, + // | left: { end: 300 } + // | } + // | }).play(); + // + // example: + // | dojo.query(".grue").animateProperty({ + // | auto:true, + // | properties: { + // | height:240 + // | } + // | }).onclick(handler); + return this._anim(dojo, "animateProperty", args); // dojo.Animation|dojo.NodeList + }, + + anim: function( /*Object*/ properties, + /*Integer?*/ duration, + /*Function?*/ easing, + /*Function?*/ onEnd, + /*Integer?*/ delay){ + // summary: + // Animate one or more CSS properties for all nodes in this list. + // The returned animation object will already be playing when it + // is returned. See the docs for `dojo.anim` for full details. + // properties: Object + // the properties to animate. does NOT support the `auto` parameter like other + // NodeList-fx methods. + // duration: Integer? + // Optional. The time to run the animations for + // easing: Function? + // Optional. The easing function to use. + // onEnd: Function? + // A function to be called when the animation ends + // delay: + // how long to delay playing the returned animation + // example: + // Another way to fade out: + // | dojo.query(".thinger").anim({ opacity: 0 }); + // example: + // animate all elements with the "thigner" class to a width of 500 + // pixels over half a second + // | dojo.query(".thinger").anim({ width: 500 }, 700); + var canim = dojo.fx.combine( + this.map(function(item){ + return dojo.animateProperty({ + node: item, + properties: properties, + duration: duration||350, + easing: easing + }); + }) + ); + if(onEnd){ + dojo.connect(canim, "onEnd", onEnd); + } + return canim.play(delay||0); // dojo.Animation + } +}); + +} + +if(!dojo._hasResource["dojo.colors"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.colors"] = true; +dojo.provide("dojo.colors"); + +//TODO: this module appears to break naming conventions + +/*===== +dojo.colors = { + // summary: Color utilities +} +=====*/ + +(function(){ + // this is a standard conversion prescribed by the CSS3 Color Module + var hue2rgb = function(m1, m2, h){ + if(h < 0){ ++h; } + if(h > 1){ --h; } + var h6 = 6 * h; + if(h6 < 1){ return m1 + (m2 - m1) * h6; } + if(2 * h < 1){ return m2; } + if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; } + return m1; + }; + + dojo.colorFromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){ + // summary: + // get rgb(a) array from css-style color declarations + // description: + // this function can handle all 4 CSS3 Color Module formats: rgb, + // rgba, hsl, hsla, including rgb(a) with percentage values. + var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/); + if(m){ + var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1], a; + if((t == "rgb" && l == 3) || (t == "rgba" && l == 4)){ + var r = c[0]; + if(r.charAt(r.length - 1) == "%"){ + // 3 rgb percentage values + a = dojo.map(c, function(x){ + return parseFloat(x) * 2.56; + }); + if(l == 4){ a[3] = c[3]; } + return dojo.colorFromArray(a, obj); // dojo.Color + } + return dojo.colorFromArray(c, obj); // dojo.Color + } + if((t == "hsl" && l == 3) || (t == "hsla" && l == 4)){ + // normalize hsl values + var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360, + S = parseFloat(c[1]) / 100, + L = parseFloat(c[2]) / 100, + // calculate rgb according to the algorithm + // recommended by the CSS3 Color Module + m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S, + m1 = 2 * L - m2; + a = [ + hue2rgb(m1, m2, H + 1 / 3) * 256, + hue2rgb(m1, m2, H) * 256, + hue2rgb(m1, m2, H - 1 / 3) * 256, + 1 + ]; + if(l == 4){ a[3] = c[3]; } + return dojo.colorFromArray(a, obj); // dojo.Color + } + } + return null; // dojo.Color + }; + + var confine = function(c, low, high){ + // summary: + // sanitize a color component by making sure it is a number, + // and clamping it to valid values + c = Number(c); + return isNaN(c) ? high : c < low ? low : c > high ? high : c; // Number + }; + + dojo.Color.prototype.sanitize = function(){ + // summary: makes sure that the object has correct attributes + var t = this; + t.r = Math.round(confine(t.r, 0, 255)); + t.g = Math.round(confine(t.g, 0, 255)); + t.b = Math.round(confine(t.b, 0, 255)); + t.a = confine(t.a, 0, 1); + return this; // dojo.Color + }; +})(); + + +dojo.colors.makeGrey = function(/*Number*/ g, /*Number?*/ a){ + // summary: creates a greyscale color with an optional alpha + return dojo.colorFromArray([g, g, g, a]); +}; + +// mixin all CSS3 named colors not already in _base, along with SVG 1.0 variant spellings +dojo.mixin(dojo.Color.named, { + aliceblue: [240,248,255], + antiquewhite: [250,235,215], + aquamarine: [127,255,212], + azure: [240,255,255], + beige: [245,245,220], + bisque: [255,228,196], + blanchedalmond: [255,235,205], + blueviolet: [138,43,226], + brown: [165,42,42], + burlywood: [222,184,135], + cadetblue: [95,158,160], + chartreuse: [127,255,0], + chocolate: [210,105,30], + coral: [255,127,80], + cornflowerblue: [100,149,237], + cornsilk: [255,248,220], + crimson: [220,20,60], + cyan: [0,255,255], + darkblue: [0,0,139], + darkcyan: [0,139,139], + darkgoldenrod: [184,134,11], + darkgray: [169,169,169], + darkgreen: [0,100,0], + darkgrey: [169,169,169], + darkkhaki: [189,183,107], + darkmagenta: [139,0,139], + darkolivegreen: [85,107,47], + darkorange: [255,140,0], + darkorchid: [153,50,204], + darkred: [139,0,0], + darksalmon: [233,150,122], + darkseagreen: [143,188,143], + darkslateblue: [72,61,139], + darkslategray: [47,79,79], + darkslategrey: [47,79,79], + darkturquoise: [0,206,209], + darkviolet: [148,0,211], + deeppink: [255,20,147], + deepskyblue: [0,191,255], + dimgray: [105,105,105], + dimgrey: [105,105,105], + dodgerblue: [30,144,255], + firebrick: [178,34,34], + floralwhite: [255,250,240], + forestgreen: [34,139,34], + gainsboro: [220,220,220], + ghostwhite: [248,248,255], + gold: [255,215,0], + goldenrod: [218,165,32], + greenyellow: [173,255,47], + grey: [128,128,128], + honeydew: [240,255,240], + hotpink: [255,105,180], + indianred: [205,92,92], + indigo: [75,0,130], + ivory: [255,255,240], + khaki: [240,230,140], + lavender: [230,230,250], + lavenderblush: [255,240,245], + lawngreen: [124,252,0], + lemonchiffon: [255,250,205], + lightblue: [173,216,230], + lightcoral: [240,128,128], + lightcyan: [224,255,255], + lightgoldenrodyellow: [250,250,210], + lightgray: [211,211,211], + lightgreen: [144,238,144], + lightgrey: [211,211,211], + lightpink: [255,182,193], + lightsalmon: [255,160,122], + lightseagreen: [32,178,170], + lightskyblue: [135,206,250], + lightslategray: [119,136,153], + lightslategrey: [119,136,153], + lightsteelblue: [176,196,222], + lightyellow: [255,255,224], + limegreen: [50,205,50], + linen: [250,240,230], + magenta: [255,0,255], + mediumaquamarine: [102,205,170], + mediumblue: [0,0,205], + mediumorchid: [186,85,211], + mediumpurple: [147,112,219], + mediumseagreen: [60,179,113], + mediumslateblue: [123,104,238], + mediumspringgreen: [0,250,154], + mediumturquoise: [72,209,204], + mediumvioletred: [199,21,133], + midnightblue: [25,25,112], + mintcream: [245,255,250], + mistyrose: [255,228,225], + moccasin: [255,228,181], + navajowhite: [255,222,173], + oldlace: [253,245,230], + olivedrab: [107,142,35], + orange: [255,165,0], + orangered: [255,69,0], + orchid: [218,112,214], + palegoldenrod: [238,232,170], + palegreen: [152,251,152], + paleturquoise: [175,238,238], + palevioletred: [219,112,147], + papayawhip: [255,239,213], + peachpuff: [255,218,185], + peru: [205,133,63], + pink: [255,192,203], + plum: [221,160,221], + powderblue: [176,224,230], + rosybrown: [188,143,143], + royalblue: [65,105,225], + saddlebrown: [139,69,19], + salmon: [250,128,114], + sandybrown: [244,164,96], + seagreen: [46,139,87], + seashell: [255,245,238], + sienna: [160,82,45], + skyblue: [135,206,235], + slateblue: [106,90,205], + slategray: [112,128,144], + slategrey: [112,128,144], + snow: [255,250,250], + springgreen: [0,255,127], + steelblue: [70,130,180], + tan: [210,180,140], + thistle: [216,191,216], + tomato: [255,99,71], + transparent: [0, 0, 0, 0], + turquoise: [64,224,208], + violet: [238,130,238], + wheat: [245,222,179], + whitesmoke: [245,245,245], + yellowgreen: [154,205,50] +}); + +} + +if(!dojo._hasResource["dojo.i18n"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.i18n"] = true; +dojo.provide("dojo.i18n"); + +/*===== +dojo.i18n = { + // summary: Utility classes to enable loading of resources for internationalization (i18n) +}; +=====*/ + +dojo.i18n.getLocalization = function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){ + // summary: + // Returns an Object containing the localization for a given resource + // bundle in a package, matching the specified locale. + // description: + // Returns a hash containing name/value pairs in its prototypesuch + // that values can be easily overridden. Throws an exception if the + // bundle is not found. Bundle must have already been loaded by + // `dojo.requireLocalization()` or by a build optimization step. NOTE: + // try not to call this method as part of an object property + // definition (`var foo = { bar: dojo.i18n.getLocalization() }`). In + // some loading situations, the bundle may not be available in time + // for the object definition. Instead, call this method inside a + // function that is run after all modules load or the page loads (like + // in `dojo.addOnLoad()`), or in a widget lifecycle method. + // packageName: + // package which is associated with this resource + // bundleName: + // the base filename of the resource bundle (without the ".js" suffix) + // locale: + // the variant to load (optional). By default, the locale defined by + // the host environment: dojo.locale + + locale = dojo.i18n.normalizeLocale(locale); + + // look for nearest locale match + var elements = locale.split('-'); + var module = [packageName,"nls",bundleName].join('.'); + var bundle = dojo._loadedModules[module]; + if(bundle){ + var localization; + for(var i = elements.length; i > 0; i--){ + var loc = elements.slice(0, i).join('_'); + if(bundle[loc]){ + localization = bundle[loc]; + break; + } + } + if(!localization){ + localization = bundle.ROOT; + } + + // make a singleton prototype so that the caller won't accidentally change the values globally + if(localization){ + var clazz = function(){}; + clazz.prototype = localization; + return new clazz(); // Object + } + } + + throw new Error("Bundle not found: " + bundleName + " in " + packageName+" , locale=" + locale); +}; + +dojo.i18n.normalizeLocale = function(/*String?*/locale){ + // summary: + // Returns canonical form of locale, as used by Dojo. + // + // description: + // All variants are case-insensitive and are separated by '-' as specified in [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt). + // If no locale is specified, the dojo.locale is returned. dojo.locale is defined by + // the user agent's locale unless overridden by djConfig. + + var result = locale ? locale.toLowerCase() : dojo.locale; + if(result == "root"){ + result = "ROOT"; + } + return result; // String +}; + +dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){ + // summary: + // See dojo.requireLocalization() + // description: + // Called by the bootstrap, but factored out so that it is only + // included in the build when needed. + + var targetLocale = dojo.i18n.normalizeLocale(locale); + var bundlePackage = [moduleName, "nls", bundleName].join("."); + // NOTE: + // When loading these resources, the packaging does not match what is + // on disk. This is an implementation detail, as this is just a + // private data structure to hold the loaded resources. e.g. + // `tests/hello/nls/en-us/salutations.js` is loaded as the object + // `tests.hello.nls.salutations.en_us={...}` The structure on disk is + // intended to be most convenient for developers and translators, but + // in memory it is more logical and efficient to store in a different + // order. Locales cannot use dashes, since the resulting path will + // not evaluate as valid JS, so we translate them to underscores. + + //Find the best-match locale to load if we have available flat locales. + var bestLocale = ""; + if(availableFlatLocales){ + var flatLocales = availableFlatLocales.split(","); + for(var i = 0; i < flatLocales.length; i++){ + //Locale must match from start of string. + //Using ["indexOf"] so customBase builds do not see + //this as a dojo._base.array dependency. + if(targetLocale["indexOf"](flatLocales[i]) == 0){ + if(flatLocales[i].length > bestLocale.length){ + bestLocale = flatLocales[i]; + } + } + } + if(!bestLocale){ + bestLocale = "ROOT"; + } + } + + //See if the desired locale is already loaded. + var tempLocale = availableFlatLocales ? bestLocale : targetLocale; + var bundle = dojo._loadedModules[bundlePackage]; + var localizedBundle = null; + if(bundle){ + if(dojo.config.localizationComplete && bundle._built){return;} + var jsLoc = tempLocale.replace(/-/g, '_'); + var translationPackage = bundlePackage+"."+jsLoc; + localizedBundle = dojo._loadedModules[translationPackage]; + } + + if(!localizedBundle){ + bundle = dojo["provide"](bundlePackage); + var syms = dojo._getModuleSymbols(moduleName); + var modpath = syms.concat("nls").join("/"); + var parent; + + dojo.i18n._searchLocalePath(tempLocale, availableFlatLocales, function(loc){ + var jsLoc = loc.replace(/-/g, '_'); + var translationPackage = bundlePackage + "." + jsLoc; + var loaded = false; + if(!dojo._loadedModules[translationPackage]){ + // Mark loaded whether it's found or not, so that further load attempts will not be made + dojo["provide"](translationPackage); + var module = [modpath]; + if(loc != "ROOT"){module.push(loc);} + module.push(bundleName); + var filespec = module.join("/") + '.js'; + loaded = dojo._loadPath(filespec, null, function(hash){ + // Use singleton with prototype to point to parent bundle, then mix-in result from loadPath + var clazz = function(){}; + clazz.prototype = parent; + bundle[jsLoc] = new clazz(); + for(var j in hash){ bundle[jsLoc][j] = hash[j]; } + }); + }else{ + loaded = true; + } + if(loaded && bundle[jsLoc]){ + parent = bundle[jsLoc]; + }else{ + bundle[jsLoc] = parent; + } + + if(availableFlatLocales){ + //Stop the locale path searching if we know the availableFlatLocales, since + //the first call to this function will load the only bundle that is needed. + return true; + } + }); + } + + //Save the best locale bundle as the target locale bundle when we know the + //the available bundles. + if(availableFlatLocales && targetLocale != bestLocale){ + bundle[targetLocale.replace(/-/g, '_')] = bundle[bestLocale.replace(/-/g, '_')]; + } +}; + +(function(){ + // If other locales are used, dojo.requireLocalization should load them as + // well, by default. + // + // Override dojo.requireLocalization to do load the default bundle, then + // iterate through the extraLocale list and load those translations as + // well, unless a particular locale was requested. + + var extra = dojo.config.extraLocale; + if(extra){ + if(!extra instanceof Array){ + extra = [extra]; + } + + var req = dojo.i18n._requireLocalization; + dojo.i18n._requireLocalization = function(m, b, locale, availableFlatLocales){ + req(m,b,locale, availableFlatLocales); + if(locale){return;} + for(var i=0; i 0; i--){ + searchlist.push(elements.slice(0, i).join('-')); + } + searchlist.push(false); + if(down){searchlist.reverse();} + + for(var j = searchlist.length - 1; j >= 0; j--){ + var loc = searchlist[j] || "ROOT"; + var stop = searchFunc(loc); + if(stop){ break; } + } +}; + +dojo.i18n._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated){ + // summary: + // Load built, flattened resource bundles, if available for all + // locales used in the page. Only called by built layer files. + + function preload(locale){ + locale = dojo.i18n.normalizeLocale(locale); + dojo.i18n._searchLocalePath(locale, true, function(loc){ + for(var i=0; i= 0){ + dojo.removeClass(this._cells[this._selectedCell].node, "dijitPaletteCellSelected"); + } + this._selectedCell = -1; + + // search for cell matching specified value + if(value){ + for(var i = 0; i < this._cells.length; i++){ + if(value == this._cells[i].dye.getValue()){ + this._selectedCell = i; + this.value = value; + + dojo.addClass(this._cells[i].node, "dijitPaletteCellSelected"); + + if(priorityChange || priorityChange === undefined){ + this.onChange(value); + } + + break; + } + } + } + }, + + onChange: function(value){ + // summary: + // Callback when a cell is selected. + // value: String + // Value corresponding to cell. + }, + + _navigateByKey: function(increment, typeCount){ + // summary: + // This is the callback for typematic. + // It changes the focus and the highlighed cell. + // increment: + // How much the key is navigated. + // typeCount: + // How many times typematic has fired. + // tags: + // private + + // typecount == -1 means the key is released. + if(typeCount == -1){ return; } + + var newFocusIndex = this._currentFocus.index + increment; + if(newFocusIndex < this._cells.length && newFocusIndex > -1){ + var focusNode = this._cells[newFocusIndex].node; + this._setCurrent(focusNode); + + // Actually focus the node, for the benefit of screen readers. + // Use setTimeout because IE doesn't like changing focus inside of an event handler + setTimeout(dojo.hitch(dijit, "focus", focusNode), 0); + } + }, + + _getDye: function(/*DomNode*/ cell){ + // summary: + // Get JS object for given cell DOMNode + + return this._cells[cell.index].dye; + } +}); + +/*===== +dojo.declare("dijit.Dye", + null, + { + // summary: + // Interface for the JS Object associated with a palette cell (i.e. DOMNode) + + constructor: function(alias){ + // summary: + // Initialize according to value or alias like "white" + // alias: String + }, + + getValue: function(){ + // summary: + // Return "value" of cell; meaning of "value" varies by subclass. + // description: + // For example color hex value, emoticon ascii value etc, entity hex value. + }, + + fillCell: function(cell, blankGif){ + // summary: + // Add cell DOMNode inner structure + // cell: DomNode + // The surrounding cell + // blankGif: String + // URL for blank cell image + } + } +); +=====*/ + +} + +if(!dojo._hasResource["dijit.ColorPalette"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.ColorPalette"] = true; +dojo.provide("dijit.ColorPalette"); + + + + + + + + + + +dojo.declare("dijit.ColorPalette", + [dijit._Widget, dijit._Templated, dijit._PaletteMixin], + { + // summary: + // A keyboard accessible color-picking widget + // description: + // Grid showing various colors, so the user can pick a certain color. + // Can be used standalone, or as a popup. + // + // example: + // |
    + // + // example: + // | var picker = new dijit.ColorPalette({ },srcNode); + // | picker.startup(); + + + // palette: String + // Size of grid, either "7x10" or "3x4". + palette: "7x10", + + // _palettes: [protected] Map + // This represents the value of the colors. + // The first level is a hashmap of the different palettes available. + // The next two dimensions represent the columns and rows of colors. + _palettes: { + "7x10": [["white", "seashell", "cornsilk", "lemonchiffon","lightyellow", "palegreen", "paleturquoise", "lightcyan", "lavender", "plum"], + ["lightgray", "pink", "bisque", "moccasin", "khaki", "lightgreen", "lightseagreen", "lightskyblue", "cornflowerblue", "violet"], + ["silver", "lightcoral", "sandybrown", "orange", "palegoldenrod", "chartreuse", "mediumturquoise", "skyblue", "mediumslateblue","orchid"], + ["gray", "red", "orangered", "darkorange", "yellow", "limegreen", "darkseagreen", "royalblue", "slateblue", "mediumorchid"], + ["dimgray", "crimson", "chocolate", "coral", "gold", "forestgreen", "seagreen", "blue", "blueviolet", "darkorchid"], + ["darkslategray","firebrick","saddlebrown", "sienna", "olive", "green", "darkcyan", "mediumblue","darkslateblue", "darkmagenta" ], + ["black", "darkred", "maroon", "brown", "darkolivegreen", "darkgreen", "midnightblue", "navy", "indigo", "purple"]], + + "3x4": [["white", "lime", "green", "blue"], + ["silver", "yellow", "fuchsia", "navy"], + ["gray", "red", "purple", "black"]] + }, + + // _imagePaths: [protected] Map + // This is stores the path to the palette images + _imagePaths: { + "7x10": dojo.moduleUrl("dijit.themes", "a11y/colors7x10.png"), + "3x4": dojo.moduleUrl("dijit.themes", "a11y/colors3x4.png"), + "7x10-rtl": dojo.moduleUrl("dijit.themes", "a11y/colors7x10-rtl.png"), + "3x4-rtl": dojo.moduleUrl("dijit.themes", "a11y/colors3x4-rtl.png") + }, + + // templateString: String + // The template of this widget. + templateString: dojo.cache("dijit", "templates/ColorPalette.html", "
    \n\t\"\"/\n\t\n\t\t\n\t
    \n
    \n"), + + baseClass: "dijitColorPalette", + + dyeClass: 'dijit._Color', + + buildRendering: function(){ + // Instantiate the template, which makes a skeleton into which we'll insert a bunch of + // nodes + + this.inherited(arguments); + + this.imageNode.setAttribute("src", this._imagePaths[this.palette + (this.isLeftToRight() ? "" : "-rtl")].toString()); + + var i18nColorNames = dojo.i18n.getLocalization("dojo", "colors", this.lang); + this._preparePalette( + this._palettes[this.palette], + i18nColorNames + ); + } +}); + +dojo.declare("dijit._Color", dojo.Color, + // summary: + // Object associated with each cell in a ColorPalette palette. + // Implements dijit.Dye. + { + constructor: function(/*String*/alias){ + this._alias = alias; + this.setColor(dojo.Color.named[alias]); + }, + + getValue: function(){ + // summary: + // Note that although dijit._Color is initialized with a value like "white" getValue() always + // returns a hex value + return this.toHex(); + }, + + fillCell: function(/*DOMNode*/ cell, /*String*/ blankGif){ + dojo.create("img", { + src: blankGif, + "class": "dijitPaletteImg", + alt: this._alias + }, cell); + } + } +); + +} + +if(!dojo._hasResource["dojo.dnd.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.common"] = true; +dojo.provide("dojo.dnd.common"); + +dojo.dnd.getCopyKeyState = dojo.isCopyKey; + +dojo.dnd._uniqueId = 0; +dojo.dnd.getUniqueId = function(){ + // summary: + // returns a unique string for use with any DOM element + var id; + do{ + id = dojo._scopeName + "Unique" + (++dojo.dnd._uniqueId); + }while(dojo.byId(id)); + return id; +}; + +dojo.dnd._empty = {}; + +dojo.dnd.isFormElement = function(/*Event*/ e){ + // summary: + // returns true if user clicked on a form element + var t = e.target; + if(t.nodeType == 3 /*TEXT_NODE*/){ + t = t.parentNode; + } + return " button textarea input select option ".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean +}; + +} + +if(!dojo._hasResource["dojo.dnd.autoscroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.autoscroll"] = true; +dojo.provide("dojo.dnd.autoscroll"); + +dojo.dnd.getViewport = function(){ + // summary: + // Returns a viewport size (visible part of the window) + + // TODO: remove this when getViewport() moved to dojo core, see #7028 + + // FIXME: need more docs!! + var d = dojo.doc, dd = d.documentElement, w = window, b = dojo.body(); + if(dojo.isMozilla){ + return {w: dd.clientWidth, h: w.innerHeight}; // Object + }else if(!dojo.isOpera && w.innerWidth){ + return {w: w.innerWidth, h: w.innerHeight}; // Object + }else if (!dojo.isOpera && dd && dd.clientWidth){ + return {w: dd.clientWidth, h: dd.clientHeight}; // Object + }else if (b.clientWidth){ + return {w: b.clientWidth, h: b.clientHeight}; // Object + } + return null; // Object +}; + +dojo.dnd.V_TRIGGER_AUTOSCROLL = 32; +dojo.dnd.H_TRIGGER_AUTOSCROLL = 32; + +dojo.dnd.V_AUTOSCROLL_VALUE = 16; +dojo.dnd.H_AUTOSCROLL_VALUE = 16; + +dojo.dnd.autoScroll = function(e){ + // summary: + // a handler for onmousemove event, which scrolls the window, if + // necesary + // e: Event + // onmousemove event + + // FIXME: needs more docs! + var v = dojo.dnd.getViewport(), dx = 0, dy = 0; + if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){ + dx = -dojo.dnd.H_AUTOSCROLL_VALUE; + }else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){ + dx = dojo.dnd.H_AUTOSCROLL_VALUE; + } + if(e.clientY < dojo.dnd.V_TRIGGER_AUTOSCROLL){ + dy = -dojo.dnd.V_AUTOSCROLL_VALUE; + }else if(e.clientY > v.h - dojo.dnd.V_TRIGGER_AUTOSCROLL){ + dy = dojo.dnd.V_AUTOSCROLL_VALUE; + } + window.scrollBy(dx, dy); +}; + +dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1}; +dojo.dnd._validOverflow = {"auto": 1, "scroll": 1}; + +dojo.dnd.autoScrollNodes = function(e){ + // summary: + // a handler for onmousemove event, which scrolls the first avaialble + // Dom element, it falls back to dojo.dnd.autoScroll() + // e: Event + // onmousemove event + + // FIXME: needs more docs! + for(var n = e.target; n;){ + if(n.nodeType == 1 && (n.tagName.toLowerCase() in dojo.dnd._validNodes)){ + var s = dojo.getComputedStyle(n); + if(s.overflow.toLowerCase() in dojo.dnd._validOverflow){ + var b = dojo._getContentBox(n, s), t = dojo.position(n, true); + //console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop); + var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2), + h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2), + rx = e.pageX - t.x, ry = e.pageY - t.y, dx = 0, dy = 0; + if(dojo.isWebKit || dojo.isOpera){ + // FIXME: this code should not be here, it should be taken into account + // either by the event fixing code, or the dojo.position() + // FIXME: this code doesn't work on Opera 9.5 Beta + rx += dojo.body().scrollLeft, ry += dojo.body().scrollTop; + } + if(rx > 0 && rx < b.w){ + if(rx < w){ + dx = -w; + }else if(rx > b.w - w){ + dx = w; + } + } + //console.log("ry =", ry, "b.h =", b.h, "h =", h); + if(ry > 0 && ry < b.h){ + if(ry < h){ + dy = -h; + }else if(ry > b.h - h){ + dy = h; + } + } + var oldLeft = n.scrollLeft, oldTop = n.scrollTop; + n.scrollLeft = n.scrollLeft + dx; + n.scrollTop = n.scrollTop + dy; + if(oldLeft != n.scrollLeft || oldTop != n.scrollTop){ return; } + } + } + try{ + n = n.parentNode; + }catch(x){ + n = null; + } + } + dojo.dnd.autoScroll(e); +}; + +} + +if(!dojo._hasResource["dojo.dnd.Mover"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.Mover"] = true; +dojo.provide("dojo.dnd.Mover"); + + + + +dojo.declare("dojo.dnd.Mover", null, { + constructor: function(node, e, host){ + // summary: + // an object, which makes a node follow the mouse. + // Used as a default mover, and as a base class for custom movers. + // node: Node + // a node (or node's id) to be moved + // e: Event + // a mouse event, which started the move; + // only pageX and pageY properties are used + // host: Object? + // object which implements the functionality of the move, + // and defines proper events (onMoveStart and onMoveStop) + this.node = dojo.byId(node); + this.marginBox = {l: e.pageX, t: e.pageY}; + this.mouseButton = e.button; + var h = this.host = host, d = node.ownerDocument, + firstEvent = dojo.connect(d, "onmousemove", this, "onFirstMove"); + this.events = [ + dojo.connect(d, "onmousemove", this, "onMouseMove"), + dojo.connect(d, "onmouseup", this, "onMouseUp"), + // cancel text selection and text dragging + dojo.connect(d, "ondragstart", dojo.stopEvent), + dojo.connect(d.body, "onselectstart", dojo.stopEvent), + firstEvent + ]; + // notify that the move has started + if(h && h.onMoveStart){ + h.onMoveStart(this); + } + }, + // mouse event processors + onMouseMove: function(e){ + // summary: + // event processor for onmousemove + // e: Event + // mouse event + dojo.dnd.autoScroll(e); + var m = this.marginBox; + this.host.onMove(this, {l: m.l + e.pageX, t: m.t + e.pageY}, e); + dojo.stopEvent(e); + }, + onMouseUp: function(e){ + if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ? + e.button == 0 : this.mouseButton == e.button){ + this.destroy(); + } + dojo.stopEvent(e); + }, + // utilities + onFirstMove: function(e){ + // summary: + // makes the node absolute; it is meant to be called only once. + // relative and absolutely positioned nodes are assumed to use pixel units + var s = this.node.style, l, t, h = this.host; + switch(s.position){ + case "relative": + case "absolute": + // assume that left and top values are in pixels already + l = Math.round(parseFloat(s.left)) || 0; + t = Math.round(parseFloat(s.top)) || 0; + break; + default: + s.position = "absolute"; // enforcing the absolute mode + var m = dojo.marginBox(this.node); + // event.pageX/pageY (which we used to generate the initial + // margin box) includes padding and margin set on the body. + // However, setting the node's position to absolute and then + // doing dojo.marginBox on it *doesn't* take that additional + // space into account - so we need to subtract the combined + // padding and margin. We use getComputedStyle and + // _getMarginBox/_getContentBox to avoid the extra lookup of + // the computed style. + var b = dojo.doc.body; + var bs = dojo.getComputedStyle(b); + var bm = dojo._getMarginBox(b, bs); + var bc = dojo._getContentBox(b, bs); + l = m.l - (bc.l - bm.l); + t = m.t - (bc.t - bm.t); + break; + } + this.marginBox.l = l - this.marginBox.l; + this.marginBox.t = t - this.marginBox.t; + if(h && h.onFirstMove){ + h.onFirstMove(this, e); + } + dojo.disconnect(this.events.pop()); + }, + destroy: function(){ + // summary: + // stops the move, deletes all references, so the object can be garbage-collected + dojo.forEach(this.events, dojo.disconnect); + // undo global settings + var h = this.host; + if(h && h.onMoveStop){ + h.onMoveStop(this); + } + // destroy objects + this.events = this.node = this.host = null; + } +}); + +} + +if(!dojo._hasResource["dojo.dnd.Moveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.Moveable"] = true; +dojo.provide("dojo.dnd.Moveable"); + + + +/*===== +dojo.declare("dojo.dnd.__MoveableArgs", [], { + // handle: Node||String + // A node (or node's id), which is used as a mouse handle. + // If omitted, the node itself is used as a handle. + handle: null, + + // delay: Number + // delay move by this number of pixels + delay: 0, + + // skip: Boolean + // skip move of form elements + skip: false, + + // mover: Object + // a constructor of custom Mover + mover: dojo.dnd.Mover +}); +=====*/ + +dojo.declare("dojo.dnd.Moveable", null, { + // object attributes (for markup) + handle: "", + delay: 0, + skip: false, + + constructor: function(node, params){ + // summary: + // an object, which makes a node moveable + // node: Node + // a node (or node's id) to be moved + // params: dojo.dnd.__MoveableArgs? + // optional parameters + this.node = dojo.byId(node); + if(!params){ params = {}; } + this.handle = params.handle ? dojo.byId(params.handle) : null; + if(!this.handle){ this.handle = this.node; } + this.delay = params.delay > 0 ? params.delay : 0; + this.skip = params.skip; + this.mover = params.mover ? params.mover : dojo.dnd.Mover; + this.events = [ + dojo.connect(this.handle, "onmousedown", this, "onMouseDown"), + // cancel text selection and text dragging + dojo.connect(this.handle, "ondragstart", this, "onSelectStart"), + dojo.connect(this.handle, "onselectstart", this, "onSelectStart") + ]; + }, + + // markup methods + markupFactory: function(params, node){ + return new dojo.dnd.Moveable(node, params); + }, + + // methods + destroy: function(){ + // summary: + // stops watching for possible move, deletes all references, so the object can be garbage-collected + dojo.forEach(this.events, dojo.disconnect); + this.events = this.node = this.handle = null; + }, + + // mouse event processors + onMouseDown: function(e){ + // summary: + // event processor for onmousedown, creates a Mover for the node + // e: Event + // mouse event + if(this.skip && dojo.dnd.isFormElement(e)){ return; } + if(this.delay){ + this.events.push( + dojo.connect(this.handle, "onmousemove", this, "onMouseMove"), + dojo.connect(this.handle, "onmouseup", this, "onMouseUp") + ); + this._lastX = e.pageX; + this._lastY = e.pageY; + }else{ + this.onDragDetected(e); + } + dojo.stopEvent(e); + }, + onMouseMove: function(e){ + // summary: + // event processor for onmousemove, used only for delayed drags + // e: Event + // mouse event + if(Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay){ + this.onMouseUp(e); + this.onDragDetected(e); + } + dojo.stopEvent(e); + }, + onMouseUp: function(e){ + // summary: + // event processor for onmouseup, used only for delayed drags + // e: Event + // mouse event + for(var i = 0; i < 2; ++i){ + dojo.disconnect(this.events.pop()); + } + dojo.stopEvent(e); + }, + onSelectStart: function(e){ + // summary: + // event processor for onselectevent and ondragevent + // e: Event + // mouse event + if(!this.skip || !dojo.dnd.isFormElement(e)){ + dojo.stopEvent(e); + } + }, + + // local events + onDragDetected: function(/* Event */ e){ + // summary: + // called when the drag is detected; + // responsible for creation of the mover + new this.mover(this.node, e, this); + }, + onMoveStart: function(/* dojo.dnd.Mover */ mover){ + // summary: + // called before every move operation + dojo.publish("/dnd/move/start", [mover]); + dojo.addClass(dojo.body(), "dojoMove"); + dojo.addClass(this.node, "dojoMoveItem"); + }, + onMoveStop: function(/* dojo.dnd.Mover */ mover){ + // summary: + // called after every move operation + dojo.publish("/dnd/move/stop", [mover]); + dojo.removeClass(dojo.body(), "dojoMove"); + dojo.removeClass(this.node, "dojoMoveItem"); + }, + onFirstMove: function(/* dojo.dnd.Mover */ mover, /* Event */ e){ + // summary: + // called during the very first move notification; + // can be used to initialize coordinates, can be overwritten. + + // default implementation does nothing + }, + onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop, /* Event */ e){ + // summary: + // called during every move notification; + // should actually move the node; can be overwritten. + this.onMoving(mover, leftTop); + var s = mover.node.style; + s.left = leftTop.l + "px"; + s.top = leftTop.t + "px"; + this.onMoved(mover, leftTop); + }, + onMoving: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ + // summary: + // called before every incremental move; can be overwritten. + + // default implementation does nothing + }, + onMoved: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ + // summary: + // called after every incremental move; can be overwritten. + + // default implementation does nothing + } +}); + +} + +if(!dojo._hasResource["dojo.dnd.move"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.move"] = true; +dojo.provide("dojo.dnd.move"); + + + + +/*===== +dojo.declare("dojo.dnd.move.__constrainedMoveableArgs", [dojo.dnd.__MoveableArgs], { + // constraints: Function + // Calculates a constraint box. + // It is called in a context of the moveable object. + constraints: function(){}, + + // within: Boolean + // restrict move within boundaries. + within: false +}); +=====*/ + +dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, { + // object attributes (for markup) + constraints: function(){}, + within: false, + + // markup methods + markupFactory: function(params, node){ + return new dojo.dnd.move.constrainedMoveable(node, params); + }, + + constructor: function(node, params){ + // summary: + // an object that makes a node moveable + // node: Node + // a node (or node's id) to be moved + // params: dojo.dnd.move.__constrainedMoveableArgs? + // an optional object with additional parameters; + // the rest is passed to the base class + if(!params){ params = {}; } + this.constraints = params.constraints; + this.within = params.within; + }, + onFirstMove: function(/* dojo.dnd.Mover */ mover){ + // summary: + // called during the very first move notification; + // can be used to initialize coordinates, can be overwritten. + var c = this.constraintBox = this.constraints.call(this, mover); + c.r = c.l + c.w; + c.b = c.t + c.h; + if(this.within){ + var mb = dojo.marginBox(mover.node); + c.r -= mb.w; + c.b -= mb.h; + } + }, + onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ + // summary: + // called during every move notification; + // should actually move the node; can be overwritten. + var c = this.constraintBox, s = mover.node.style; + s.left = (leftTop.l < c.l ? c.l : c.r < leftTop.l ? c.r : leftTop.l) + "px"; + s.top = (leftTop.t < c.t ? c.t : c.b < leftTop.t ? c.b : leftTop.t) + "px"; + } +}); + +/*===== +dojo.declare("dojo.dnd.move.__boxConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], { + // box: Object + // a constraint box + box: {} +}); +=====*/ + +dojo.declare("dojo.dnd.move.boxConstrainedMoveable", dojo.dnd.move.constrainedMoveable, { + // box: + // object attributes (for markup) + box: {}, + + // markup methods + markupFactory: function(params, node){ + return new dojo.dnd.move.boxConstrainedMoveable(node, params); + }, + + constructor: function(node, params){ + // summary: + // an object, which makes a node moveable + // node: Node + // a node (or node's id) to be moved + // params: dojo.dnd.move.__boxConstrainedMoveableArgs? + // an optional object with parameters + var box = params && params.box; + this.constraints = function(){ return box; }; + } +}); + +/*===== +dojo.declare("dojo.dnd.move.__parentConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], { + // area: String + // A parent's area to restrict the move. + // Can be "margin", "border", "padding", or "content". + area: "" +}); +=====*/ + +dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constrainedMoveable, { + // area: + // object attributes (for markup) + area: "content", + + // markup methods + markupFactory: function(params, node){ + return new dojo.dnd.move.parentConstrainedMoveable(node, params); + }, + + constructor: function(node, params){ + // summary: + // an object, which makes a node moveable + // node: Node + // a node (or node's id) to be moved + // params: dojo.dnd.move.__parentConstrainedMoveableArgs? + // an optional object with parameters + var area = params && params.area; + this.constraints = function(){ + var n = this.node.parentNode, + s = dojo.getComputedStyle(n), + mb = dojo._getMarginBox(n, s); + if(area == "margin"){ + return mb; // Object + } + var t = dojo._getMarginExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + if(area == "border"){ + return mb; // Object + } + t = dojo._getBorderExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + if(area == "padding"){ + return mb; // Object + } + t = dojo._getPadExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + return mb; // Object + }; + } +}); + +// WARNING: below are obsolete objects, instead of custom movers use custom moveables (above) + +dojo.dnd.move.constrainedMover = function(fun, within){ + // summary: + // returns a constrained version of dojo.dnd.Mover + // description: + // this function produces n object, which will put a constraint on + // the margin box of dragged object in absolute coordinates + // fun: Function + // called on drag, and returns a constraint box + // within: Boolean + // if true, constraints the whole dragged object withtin the rectangle, + // otherwise the constraint is applied to the left-top corner + + dojo.deprecated("dojo.dnd.move.constrainedMover, use dojo.dnd.move.constrainedMoveable instead"); + var mover = function(node, e, notifier){ + dojo.dnd.Mover.call(this, node, e, notifier); + }; + dojo.extend(mover, dojo.dnd.Mover.prototype); + dojo.extend(mover, { + onMouseMove: function(e){ + // summary: event processor for onmousemove + // e: Event: mouse event + dojo.dnd.autoScroll(e); + var m = this.marginBox, c = this.constraintBox, + l = m.l + e.pageX, t = m.t + e.pageY; + l = l < c.l ? c.l : c.r < l ? c.r : l; + t = t < c.t ? c.t : c.b < t ? c.b : t; + this.host.onMove(this, {l: l, t: t}); + }, + onFirstMove: function(){ + // summary: called once to initialize things; it is meant to be called only once + dojo.dnd.Mover.prototype.onFirstMove.call(this); + var c = this.constraintBox = fun.call(this); + c.r = c.l + c.w; + c.b = c.t + c.h; + if(within){ + var mb = dojo.marginBox(this.node); + c.r -= mb.w; + c.b -= mb.h; + } + } + }); + return mover; // Object +}; + +dojo.dnd.move.boxConstrainedMover = function(box, within){ + // summary: + // a specialization of dojo.dnd.constrainedMover, which constrains to the specified box + // box: Object + // a constraint box (l, t, w, h) + // within: Boolean + // if true, constraints the whole dragged object withtin the rectangle, + // otherwise the constraint is applied to the left-top corner + + dojo.deprecated("dojo.dnd.move.boxConstrainedMover, use dojo.dnd.move.boxConstrainedMoveable instead"); + return dojo.dnd.move.constrainedMover(function(){ return box; }, within); // Object +}; + +dojo.dnd.move.parentConstrainedMover = function(area, within){ + // summary: + // a specialization of dojo.dnd.constrainedMover, which constrains to the parent node + // area: String + // "margin" to constrain within the parent's margin box, "border" for the border box, + // "padding" for the padding box, and "content" for the content box; "content" is the default value. + // within: Boolean + // if true, constraints the whole dragged object within the rectangle, + // otherwise the constraint is applied to the left-top corner + + dojo.deprecated("dojo.dnd.move.parentConstrainedMover, use dojo.dnd.move.parentConstrainedMoveable instead"); + var fun = function(){ + var n = this.node.parentNode, + s = dojo.getComputedStyle(n), + mb = dojo._getMarginBox(n, s); + if(area == "margin"){ + return mb; // Object + } + var t = dojo._getMarginExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + if(area == "border"){ + return mb; // Object + } + t = dojo._getBorderExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + if(area == "padding"){ + return mb; // Object + } + t = dojo._getPadExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + return mb; // Object + }; + return dojo.dnd.move.constrainedMover(fun, within); // Object +}; + +// patching functions one level up for compatibility + +dojo.dnd.constrainedMover = dojo.dnd.move.constrainedMover; +dojo.dnd.boxConstrainedMover = dojo.dnd.move.boxConstrainedMover; +dojo.dnd.parentConstrainedMover = dojo.dnd.move.parentConstrainedMover; + +} + +if(!dojo._hasResource["dojo.dnd.TimedMoveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.TimedMoveable"] = true; +dojo.provide("dojo.dnd.TimedMoveable"); + + + +/*===== +dojo.declare("dojo.dnd.__TimedMoveableArgs", [dojo.dnd.__MoveableArgs], { + // timeout: Number + // delay move by this number of ms, + // accumulating position changes during the timeout + timeout: 0 +}); +=====*/ + +(function(){ + // precalculate long expressions + var oldOnMove = dojo.dnd.Moveable.prototype.onMove; + + dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, { + // summary: + // A specialized version of Moveable to support an FPS throttling. + // This class puts an upper restriction on FPS, which may reduce + // the CPU load. The additional parameter "timeout" regulates + // the delay before actually moving the moveable object. + + // object attributes (for markup) + timeout: 40, // in ms, 40ms corresponds to 25 fps + + constructor: function(node, params){ + // summary: + // an object that makes a node moveable with a timer + // node: Node||String + // a node (or node's id) to be moved + // params: dojo.dnd.__TimedMoveableArgs + // object with additional parameters. + + // sanitize parameters + if(!params){ params = {}; } + if(params.timeout && typeof params.timeout == "number" && params.timeout >= 0){ + this.timeout = params.timeout; + } + }, + + // markup methods + markupFactory: function(params, node){ + return new dojo.dnd.TimedMoveable(node, params); + }, + + onMoveStop: function(/* dojo.dnd.Mover */ mover){ + if(mover._timer){ + // stop timer + clearTimeout(mover._timer) + // reflect the last received position + oldOnMove.call(this, mover, mover._leftTop) + } + dojo.dnd.Moveable.prototype.onMoveStop.apply(this, arguments); + }, + onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ + mover._leftTop = leftTop; + if(!mover._timer){ + var _t = this; // to avoid using dojo.hitch() + mover._timer = setTimeout(function(){ + // we don't have any pending requests + mover._timer = null; + // reflect the last received position + oldOnMove.call(_t, mover, mover._leftTop); + }, this.timeout); + } + } + }); +})(); + +} + +if(!dojo._hasResource["dijit.form._FormMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form._FormMixin"] = true; +dojo.provide("dijit.form._FormMixin"); + + + +dojo.declare("dijit.form._FormMixin", null, + { + // summary: + // Mixin for containers of form widgets (i.e. widgets that represent a single value + // and can be children of a
    node or dijit.form.Form widget) + // description: + // Can extract all the form widgets + // values and combine them into a single javascript object, or alternately + // take such an object and set the values for all the contained + // form widgets + +/*===== + // value: Object + // Name/value hash for each child widget with a name and value. + // Child widgets without names are not part of the hash. + // + // If there are multiple child widgets w/the same name, value is an array, + // unless they are radio buttons in which case value is a scalar (since only + // one radio button can be checked at a time). + // + // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure. + // + // Example: + // | { name: "John Smith", interests: ["sports", "movies"] } +=====*/ + + // TODO: + // * Repeater + // * better handling for arrays. Often form elements have names with [] like + // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...]) + // + // + + reset: function(){ + dojo.forEach(this.getDescendants(), function(widget){ + if(widget.reset){ + widget.reset(); + } + }); + }, + + validate: function(){ + // summary: + // returns if the form is valid - same as isValid - but + // provides a few additional (ui-specific) features. + // 1 - it will highlight any sub-widgets that are not + // valid + // 2 - it will call focus() on the first invalid + // sub-widget + var didFocus = false; + return dojo.every(dojo.map(this.getDescendants(), function(widget){ + // Need to set this so that "required" widgets get their + // state set. + widget._hasBeenBlurred = true; + var valid = widget.disabled || !widget.validate || widget.validate(); + if(!valid && !didFocus){ + // Set focus of the first non-valid widget + dojo.window.scrollIntoView(widget.containerNode || widget.domNode); + widget.focus(); + didFocus = true; + } + return valid; + }), function(item){ return item; }); + }, + + setValues: function(val){ + dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0"); + return this.set('value', val); + }, + _setValueAttr: function(/*object*/obj){ + // summary: + // Fill in form values from according to an Object (in the format returned by attr('value')) + + // generate map from name --> [list of widgets with that name] + var map = { }; + dojo.forEach(this.getDescendants(), function(widget){ + if(!widget.name){ return; } + var entry = map[widget.name] || (map[widget.name] = [] ); + entry.push(widget); + }); + + for(var name in map){ + if(!map.hasOwnProperty(name)){ + continue; + } + var widgets = map[name], // array of widgets w/this name + values = dojo.getObject(name, false, obj); // list of values for those widgets + + if(values === undefined){ + continue; + } + if(!dojo.isArray(values)){ + values = [ values ]; + } + if(typeof widgets[0].checked == 'boolean'){ + // for checkbox/radio, values is a list of which widgets should be checked + dojo.forEach(widgets, function(w, i){ + w.set('value', dojo.indexOf(values, w.value) != -1); + }); + }else if(widgets[0].multiple){ + // it takes an array (e.g. multi-select) + widgets[0].set('value', values); + }else{ + // otherwise, values is a list of values to be assigned sequentially to each widget + dojo.forEach(widgets, function(w, i){ + w.set('value', values[i]); + }); + } + } + + /*** + * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets) + + dojo.forEach(this.containerNode.elements, function(element){ + if(element.name == ''){return}; // like "continue" + var namePath = element.name.split("."); + var myObj=obj; + var name=namePath[namePath.length-1]; + for(var j=1,len2=namePath.length;j 1){ + if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]]=[ ]; + } // if + + nameIndex=parseInt(nameA[1]); + if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ + myObj[nameA[0]][nameIndex] = { }; + } + myObj=myObj[nameA[0]][nameIndex]; + continue; + } // repeater support ends + + if(typeof(myObj[p]) == "undefined"){ + myObj=undefined; + break; + }; + myObj=myObj[p]; + } + + if(typeof(myObj) == "undefined"){ + return; // like "continue" + } + if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){ + return; // like "continue" + } + + // TODO: widget values (just call attr('value', ...) on the widget) + + // TODO: maybe should call dojo.getNodeProp() instead + switch(element.type){ + case "checkbox": + element.checked = (name in myObj) && + dojo.some(myObj[name], function(val){ return val == element.value; }); + break; + case "radio": + element.checked = (name in myObj) && myObj[name] == element.value; + break; + case "select-multiple": + element.selectedIndex=-1; + dojo.forEach(element.options, function(option){ + option.selected = dojo.some(myObj[name], function(val){ return option.value == val; }); + }); + break; + case "select-one": + element.selectedIndex="0"; + dojo.forEach(element.options, function(option){ + option.selected = option.value == myObj[name]; + }); + break; + case "hidden": + case "text": + case "textarea": + case "password": + element.value = myObj[name] || ""; + break; + } + }); + */ + }, + + getValues: function(){ + dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0"); + return this.get('value'); + }, + _getValueAttr: function(){ + // summary: + // Returns Object representing form values. + // description: + // Returns name/value hash for each form element. + // If there are multiple elements w/the same name, value is an array, + // unless they are radio buttons in which case value is a scalar since only + // one can be checked at a time. + // + // If the name is a dot separated list (like a.b.c.d), creates a nested structure. + // Only works on widget form elements. + // example: + // | { name: "John Smith", interests: ["sports", "movies"] } + + // get widget values + var obj = { }; + dojo.forEach(this.getDescendants(), function(widget){ + var name = widget.name; + if(!name || widget.disabled){ return; } + + // Single value widget (checkbox, radio, or plain type widget + var value = widget.get('value'); + + // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays + if(typeof widget.checked == 'boolean'){ + if(/Radio/.test(widget.declaredClass)){ + // radio button + if(value !== false){ + dojo.setObject(name, value, obj); + }else{ + // give radio widgets a default of null + value = dojo.getObject(name, false, obj); + if(value === undefined){ + dojo.setObject(name, null, obj); + } + } + }else{ + // checkbox/toggle button + var ary=dojo.getObject(name, false, obj); + if(!ary){ + ary=[]; + dojo.setObject(name, ary, obj); + } + if(value !== false){ + ary.push(value); + } + } + }else{ + var prev=dojo.getObject(name, false, obj); + if(typeof prev != "undefined"){ + if(dojo.isArray(prev)){ + prev.push(value); + }else{ + dojo.setObject(name, [prev, value], obj); + } + }else{ + // unique name + dojo.setObject(name, value, obj); + } + } + }); + + /*** + * code for plain input boxes (see also dojo.formToObject, can we use that instead of this code? + * but it doesn't understand [] notation, presumably) + var obj = { }; + dojo.forEach(this.containerNode.elements, function(elm){ + if(!elm.name) { + return; // like "continue" + } + var namePath = elm.name.split("."); + var myObj=obj; + var name=namePath[namePath.length-1]; + for(var j=1,len2=namePath.length;j 1){ + if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]]=[ ]; + } // if + nameIndex=parseInt(nameA[1]); + if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ + myObj[nameA[0]][nameIndex] = { }; + } + } else if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]] = { } + } // if + + if(nameA.length == 1){ + myObj=myObj[nameA[0]]; + } else{ + myObj=myObj[nameA[0]][nameIndex]; + } // if + } // for + + if((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type == "radio" && elm.checked)){ + if(name == name.split("[")[0]){ + myObj[name]=elm.value; + } else{ + // can not set value when there is no name + } + } else if(elm.type == "checkbox" && elm.checked){ + if(typeof(myObj[name]) == 'undefined'){ + myObj[name]=[ ]; + } + myObj[name].push(elm.value); + } else if(elm.type == "select-multiple"){ + if(typeof(myObj[name]) == 'undefined'){ + myObj[name]=[ ]; + } + for(var jdx=0,len3=elm.options.length; jdx
    ", + + // Parameters on creation or updatable later + + // dialogId: String + // Id of the dialog.... DialogUnderlay's id is based on this id + dialogId: "", + + // class: String + // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay + "class": "", + + attributeMap: { id: "domNode" }, + + _setDialogIdAttr: function(id){ + dojo.attr(this.node, "id", id + "_underlay"); + }, + + _setClassAttr: function(clazz){ + this.node.className = "dijitDialogUnderlay " + clazz; + }, + + postCreate: function(){ + // summary: + // Append the underlay to the body + dojo.body().appendChild(this.domNode); + }, + + layout: function(){ + // summary: + // Sets the background to the size of the viewport + // + // description: + // Sets the background to the size of the viewport (rather than the size + // of the document) since we need to cover the whole browser window, even + // if the document is only a few lines long. + // tags: + // private + + var is = this.node.style, + os = this.domNode.style; + + // hide the background temporarily, so that the background itself isn't + // causing scrollbars to appear (might happen when user shrinks browser + // window and then we are called to resize) + os.display = "none"; + + // then resize and show + var viewport = dojo.window.getBox(); + os.top = viewport.t + "px"; + os.left = viewport.l + "px"; + is.width = viewport.w + "px"; + is.height = viewport.h + "px"; + os.display = "block"; + }, + + show: function(){ + // summary: + // Show the dialog underlay + this.domNode.style.display = "block"; + this.layout(); + this.bgIframe = new dijit.BackgroundIframe(this.domNode); + }, + + hide: function(){ + // summary: + // Hides the dialog underlay + this.bgIframe.destroy(); + this.domNode.style.display = "none"; + }, + + uninitialize: function(){ + if(this.bgIframe){ + this.bgIframe.destroy(); + } + this.inherited(arguments); + } + } +); + +} + +if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.html"] = true; +dojo.provide("dojo.html"); + +// the parser might be needed.. + + +(function(){ // private scope, sort of a namespace + + // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes + var idCounter = 0, + d = dojo; + + dojo.html._secureForInnerHtml = function(/*String*/ cont){ + // summary: + // removes !DOCTYPE and title elements from the html string. + // + // khtml is picky about dom faults, you can't attach a style or node as child of body + // must go into head, so we need to cut out those tags + // cont: + // An html string for insertion into the dom + // + return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String + }; + +/*==== + dojo.html._emptyNode = function(node){ + // summary: + // removes all child nodes from the given node + // node: DOMNode + // the parent element + }; +=====*/ + dojo.html._emptyNode = dojo.empty; + + dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){ + // summary: + // inserts the given content into the given node + // node: + // the parent element + // content: + // the content to be set on the parent element. + // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes + + // always empty + d.empty(node); + + if(cont) { + if(typeof cont == "string") { + cont = d._toDom(cont, node.ownerDocument); + } + if(!cont.nodeType && d.isArrayLike(cont)) { + // handle as enumerable, but it may shrink as we enumerate it + for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) { + d.place( cont[i], node, "last"); + } + } else { + // pass nodes, documentFragments and unknowns through to dojo.place + d.place(cont, node, "last"); + } + } + + // return DomNode + return node; + }; + + // we wrap up the content-setting operation in a object + dojo.declare("dojo.html._ContentSetter", null, + { + // node: DomNode|String + // An node which will be the parent element that we set content into + node: "", + + // content: String|DomNode|DomNode[] + // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes + content: "", + + // id: String? + // Usually only used internally, and auto-generated with each instance + id: "", + + // cleanContent: Boolean + // Should the content be treated as a full html document, + // and the real content stripped of <html>, <body> wrapper before injection + cleanContent: false, + + // extractContent: Boolean + // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection + extractContent: false, + + // parseContent: Boolean + // Should the node by passed to the parser after the new content is set + parseContent: false, + + // lifecyle methods + constructor: function(/* Object */params, /* String|DomNode */node){ + // summary: + // Provides a configurable, extensible object to wrap the setting on content on a node + // call the set() method to actually set the content.. + + // the original params are mixed directly into the instance "this" + dojo.mixin(this, params || {}); + + // give precedence to params.node vs. the node argument + // and ensure its a node, not an id string + node = this.node = dojo.byId( this.node || node ); + + if(!this.id){ + this.id = [ + "Setter", + (node) ? node.id || node.tagName : "", + idCounter++ + ].join("_"); + } + }, + set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){ + // summary: + // front-end to the set-content sequence + // cont: + // An html string, node or enumerable list of nodes for insertion into the dom + // If not provided, the object's content property will be used + if(undefined !== cont){ + this.content = cont; + } + // in the re-use scenario, set needs to be able to mixin new configuration + if(params){ + this._mixin(params); + } + + this.onBegin(); + this.setContent(); + this.onEnd(); + + return this.node; + }, + setContent: function(){ + // summary: + // sets the content on the node + + var node = this.node; + if(!node) { + // can't proceed + throw new Error(this.declaredClass + ": setContent given no node"); + } + try{ + node = dojo.html._setNodeContent(node, this.content); + }catch(e){ + // check if a domfault occurs when we are appending this.errorMessage + // like for instance if domNode is a UL and we try append a DIV + + // FIXME: need to allow the user to provide a content error message string + var errMess = this.onContentError(e); + try{ + node.innerHTML = errMess; + }catch(e){ + console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e); + } + } + // always put back the node for the next method + this.node = node; // DomNode + }, + + empty: function() { + // summary + // cleanly empty out existing content + + // destroy any widgets from a previous run + // NOTE: if you dont want this you'll need to empty + // the parseResults array property yourself to avoid bad things happenning + if(this.parseResults && this.parseResults.length) { + dojo.forEach(this.parseResults, function(w) { + if(w.destroy){ + w.destroy(); + } + }); + delete this.parseResults; + } + // this is fast, but if you know its already empty or safe, you could + // override empty to skip this step + dojo.html._emptyNode(this.node); + }, + + onBegin: function(){ + // summary + // Called after instantiation, but before set(); + // It allows modification of any of the object properties + // - including the node and content provided - before the set operation actually takes place + // This default implementation checks for cleanContent and extractContent flags to + // optionally pre-process html string content + var cont = this.content; + + if(dojo.isString(cont)){ + if(this.cleanContent){ + cont = dojo.html._secureForInnerHtml(cont); + } + + if(this.extractContent){ + var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); + if(match){ cont = match[1]; } + } + } + + // clean out the node and any cruft associated with it - like widgets + this.empty(); + + this.content = cont; + return this.node; /* DomNode */ + }, + + onEnd: function(){ + // summary + // Called after set(), when the new content has been pushed into the node + // It provides an opportunity for post-processing before handing back the node to the caller + // This default implementation checks a parseContent flag to optionally run the dojo parser over the new content + if(this.parseContent){ + // populates this.parseResults if you need those.. + this._parse(); + } + return this.node; /* DomNode */ + }, + + tearDown: function(){ + // summary + // manually reset the Setter instance if its being re-used for example for another set() + // description + // tearDown() is not called automatically. + // In normal use, the Setter instance properties are simply allowed to fall out of scope + // but the tearDown method can be called to explicitly reset this instance. + delete this.parseResults; + delete this.node; + delete this.content; + }, + + onContentError: function(err){ + return "Error occured setting content: " + err; + }, + + _mixin: function(params){ + // mix properties/methods into the instance + // TODO: the intention with tearDown is to put the Setter's state + // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params) + // so we could do something here to move the original properties aside for later restoration + var empty = {}, key; + for(key in params){ + if(key in empty){ continue; } + // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable + // .. but history shows we'll almost always guess wrong + this[key] = params[key]; + } + }, + _parse: function(){ + // summary: + // runs the dojo parser over the node contents, storing any results in this.parseResults + // Any errors resulting from parsing are passed to _onError for handling + + var rootNode = this.node; + try{ + // store the results (widgets, whatever) for potential retrieval + this.parseResults = dojo.parser.parse({ + rootNode: rootNode, + dir: this.dir, + lang: this.lang + }); + }catch(e){ + this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id); + } + }, + + _onError: function(type, err, consoleText){ + // summary: + // shows user the string that is returned by on[type]Error + // overide/implement on[type]Error and return your own string to customize + var errText = this['on' + type + 'Error'].call(this, err); + if(consoleText){ + console.error(consoleText, err); + }else if(errText){ // a empty string won't change current content + dojo.html._setNodeContent(this.node, errText, true); + } + } + }); // end dojo.declare() + + dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){ + // summary: + // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only") + // may be a better choice for simple HTML insertion. + // description: + // Unless you need to use the params capabilities of this method, you should use + // dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting + // an HTML string into the DOM, but it only handles inserting an HTML string as DOM + // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions + // or the other capabilities as defined by the params object for this method. + // node: + // the parent element that will receive the content + // cont: + // the content to be set on the parent element. + // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes + // params: + // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter + // example: + // A safe string/node/nodelist content replacement/injection with hooks for extension + // Example Usage: + // dojo.html.set(node, "some string"); + // dojo.html.set(node, contentNode, {options}); + // dojo.html.set(node, myNode.childNodes, {options}); + if(undefined == cont){ + console.warn("dojo.html.set: no cont argument provided, using empty string"); + cont = ""; + } + if(!params){ + // simple and fast + return dojo.html._setNodeContent(node, cont, true); + }else{ + // more options but slower + // note the arguments are reversed in order, to match the convention for instantiation via the parser + var op = new dojo.html._ContentSetter(dojo.mixin( + params, + { content: cont, node: node } + )); + return op.set(); + } + }; +})(); + +} + +if(!dojo._hasResource["dijit.layout.ContentPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.ContentPane"] = true; +dojo.provide("dijit.layout.ContentPane"); + + + + // for dijit.layout.marginBox2contentBox() + + + + + + +dojo.declare( + "dijit.layout.ContentPane", dijit._Widget, +{ + // summary: + // A widget that acts as a container for mixed HTML and widgets, and includes an Ajax interface + // description: + // A widget that can be used as a stand alone widget + // or as a base class for other widgets. + // + // Handles replacement of document fragment using either external uri or javascript + // generated markup or DOM content, instantiating widgets within that content. + // Don't confuse it with an iframe, it only needs/wants document fragments. + // It's useful as a child of LayoutContainer, SplitContainer, or TabContainer. + // But note that those classes can contain any widget as a child. + // example: + // Some quick samples: + // To change the innerHTML use .set('content', '<b>new content</b>') + // + // Or you can send it a NodeList, .set('content', dojo.query('div [class=selected]', userSelection)) + // please note that the nodes in NodeList will copied, not moved + // + // To do a ajax update use .set('href', url) + + // href: String + // The href of the content that displays now. + // Set this at construction if you want to load data externally when the + // pane is shown. (Set preload=true to load it immediately.) + // Changing href after creation doesn't have any effect; Use set('href', ...); + href: "", + +/*===== + // content: String || DomNode || NodeList || dijit._Widget + // The innerHTML of the ContentPane. + // Note that the initialization parameter / argument to attr("content", ...) + // can be a String, DomNode, Nodelist, or _Widget. + content: "", +=====*/ + + // extractContent: Boolean + // Extract visible content from inside of <body> .... </body>. + // I.e., strip <html> and <head> (and it's contents) from the href + extractContent: false, + + // parseOnLoad: Boolean + // Parse content and create the widgets, if any. + parseOnLoad: true, + + // preventCache: Boolean + // Prevent caching of data from href's by appending a timestamp to the href. + preventCache: false, + + // preload: Boolean + // Force load of data on initialization even if pane is hidden. + preload: false, + + // refreshOnShow: Boolean + // Refresh (re-download) content when pane goes from hidden to shown + refreshOnShow: false, + + // loadingMessage: String + // Message that shows while downloading + loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>", + + // errorMessage: String + // Message that shows if an error occurs + errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>", + + // isLoaded: [readonly] Boolean + // True if the ContentPane has data in it, either specified + // during initialization (via href or inline content), or set + // via attr('content', ...) / attr('href', ...) + // + // False if it doesn't have any content, or if ContentPane is + // still in the process of downloading href. + isLoaded: false, + + baseClass: "dijitContentPane", + + // doLayout: Boolean + // - false - don't adjust size of children + // - true - if there is a single visible child widget, set it's size to + // however big the ContentPane is + doLayout: true, + + // ioArgs: Object + // Parameters to pass to xhrGet() request, for example: + // | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}"> + ioArgs: {}, + + // isContainer: [protected] Boolean + // Indicates that this widget acts as a "parent" to the descendant widgets. + // When the parent is started it will call startup() on the child widgets. + // See also `isLayoutContainer`. + isContainer: true, + + // isLayoutContainer: [protected] Boolean + // Indicates that this widget will call resize() on it's child widgets + // when they become visible. + isLayoutContainer: true, + + // onLoadDeferred: [readonly] dojo.Deferred + // This is the `dojo.Deferred` returned by attr('href', ...) and refresh(). + // Calling onLoadDeferred.addCallback() or addErrback() registers your + // callback to be called only once, when the prior attr('href', ...) call or + // the initial href parameter to the constructor finishes loading. + // + // This is different than an onLoad() handler which gets called any time any href is loaded. + onLoadDeferred: null, + + // Override _Widget's attributeMap because we don't want the title attribute (used to specify + // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the + // entire pane. + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + title: [] + }), + + postMixInProperties: function(){ + this.inherited(arguments); + var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang); + this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages); + this.errorMessage = dojo.string.substitute(this.errorMessage, messages); + + // Detect if we were initialized with data + if(!this.href && this.srcNodeRef && this.srcNodeRef.innerHTML){ + this.isLoaded = true; + } + }, + + buildRendering: function(){ + // Overrides Widget.buildRendering(). + // Since we have no template we need to set this.containerNode ourselves. + // For subclasses of ContentPane do have a template, does nothing. + this.inherited(arguments); + if(!this.containerNode){ + // make getDescendants() work + this.containerNode = this.domNode; + } + }, + + postCreate: function(){ + // remove the title attribute so it doesn't show up when hovering + // over a node + this.domNode.title = ""; + + if(!dojo.attr(this.domNode,"role")){ + dijit.setWaiRole(this.domNode, "group"); + } + + dojo.addClass(this.domNode, this.baseClass); + }, + + startup: function(){ + // summary: + // See `dijit.layout._LayoutWidget.startup` for description. + // Although ContentPane doesn't extend _LayoutWidget, it does implement + // the same API. + if(this._started){ return; } + + var parent = dijit._Contained.prototype.getParent.call(this); + this._childOfLayoutWidget = parent && parent.isLayoutContainer; + + // I need to call resize() on my child/children (when I become visible), unless + // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then. + this._needLayout = !this._childOfLayoutWidget; + + if(this.isLoaded){ + dojo.forEach(this.getChildren(), function(child){ + child.startup(); + }); + } + + if(this._isShown() || this.preload){ + this._onShow(); + } + + this.inherited(arguments); + }, + + _checkIfSingleChild: function(){ + // summary: + // Test if we have exactly one visible widget as a child, + // and if so assume that we are a container for that widget, + // and should propogate startup() and resize() calls to it. + // Skips over things like data stores since they aren't visible. + + var childNodes = dojo.query("> *", this.containerNode).filter(function(node){ + return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc.. + }), + childWidgetNodes = childNodes.filter(function(node){ + return dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId"); + }), + candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){ + return widget && widget.domNode && widget.resize; + }); + + if( + // all child nodes are widgets + childNodes.length == childWidgetNodes.length && + + // all but one are invisible (like dojo.data) + candidateWidgets.length == 1 + ){ + this._singleChild = candidateWidgets[0]; + }else{ + delete this._singleChild; + } + + // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449) + dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild); + }, + + setHref: function(/*String|Uri*/ href){ + // summary: + // Deprecated. Use set('href', ...) instead. + dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0"); + return this.set("href", href); + }, + _setHrefAttr: function(/*String|Uri*/ href){ + // summary: + // Hook so attr("href", ...) works. + // description: + // Reset the (external defined) content of this pane and replace with new url + // Note: It delays the download until widget is shown if preload is false. + // href: + // url to the page you want to get, must be within the same domain as your mainpage + + // Cancel any in-flight requests (an attr('href') will cancel any in-flight attr('href', ...)) + this.cancel(); + + this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); + + this.href = href; + + // _setHrefAttr() is called during creation and by the user, after creation. + // only in the second case do we actually load the URL; otherwise it's done in startup() + if(this._created && (this.preload || this._isShown())){ + this._load(); + }else{ + // Set flag to indicate that href needs to be loaded the next time the + // ContentPane is made visible + this._hrefChanged = true; + } + + return this.onLoadDeferred; // dojo.Deferred + }, + + setContent: function(/*String|DomNode|Nodelist*/data){ + // summary: + // Deprecated. Use set('content', ...) instead. + dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0"); + this.set("content", data); + }, + _setContentAttr: function(/*String|DomNode|Nodelist*/data){ + // summary: + // Hook to make attr("content", ...) work. + // Replaces old content with data content, include style classes from old content + // data: + // the new Content may be String, DomNode or NodeList + // + // if data is a NodeList (or an array of nodes) nodes are copied + // so you can import nodes from another document implicitly + + // clear href so we can't run refresh and clear content + // refresh should only work if we downloaded the content + this.href = ""; + + // Cancel any in-flight requests (an attr('content') will cancel any in-flight attr('href', ...)) + this.cancel(); + + // Even though user is just setting content directly, still need to define an onLoadDeferred + // because the _onLoadHandler() handler is still getting called from setContent() + this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); + + this._setContent(data || ""); + + this._isDownloaded = false; // mark that content is from a attr('content') not an attr('href') + + return this.onLoadDeferred; // dojo.Deferred + }, + _getContentAttr: function(){ + // summary: + // Hook to make attr("content") work + return this.containerNode.innerHTML; + }, + + cancel: function(){ + // summary: + // Cancels an in-flight download of content + if(this._xhrDfd && (this._xhrDfd.fired == -1)){ + this._xhrDfd.cancel(); + } + delete this._xhrDfd; // garbage collect + + this.onLoadDeferred = null; + }, + + uninitialize: function(){ + if(this._beingDestroyed){ + this.cancel(); + } + this.inherited(arguments); + }, + + destroyRecursive: function(/*Boolean*/ preserveDom){ + // summary: + // Destroy the ContentPane and its contents + + // if we have multiple controllers destroying us, bail after the first + if(this._beingDestroyed){ + return; + } + this.inherited(arguments); + }, + + resize: function(changeSize, resultSize){ + // summary: + // See `dijit.layout._LayoutWidget.resize` for description. + // Although ContentPane doesn't extend _LayoutWidget, it does implement + // the same API. + + // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is + // never called, so resize() is our trigger to do the initial href download. + if(!this._wasShown){ + this._onShow(); + } + + this._resizeCalled = true; + + // Set margin box size, unless it wasn't specified, in which case use current size. + if(changeSize){ + dojo.marginBox(this.domNode, changeSize); + } + + // Compute content box size of containerNode in case we [later] need to size our single child. + var cn = this.containerNode; + if(cn === this.domNode){ + // If changeSize or resultSize was passed to this method and this.containerNode == + // this.domNode then we can compute the content-box size without querying the node, + // which is more reliable (similar to LayoutWidget.resize) (see for example #9449). + var mb = resultSize || {}; + dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize + if(!("h" in mb) || !("w" in mb)){ + mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values + } + this._contentBox = dijit.layout.marginBox2contentBox(cn, mb); + }else{ + this._contentBox = dojo.contentBox(cn); + } + + // Make my children layout, or size my single child widget + this._layoutChildren(); + }, + + _isShown: function(){ + // summary: + // Returns true if the content is currently shown. + // description: + // If I am a child of a layout widget then it actually returns true if I've ever been visible, + // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget + // tree every call, and at least solves the performance problem on page load by deferring loading + // hidden ContentPanes until they are first shown + + if(this._childOfLayoutWidget){ + // If we are TitlePane, etc - we return that only *IF* we've been resized + if(this._resizeCalled && "open" in this){ + return this.open; + } + return this._resizeCalled; + }else if("open" in this){ + return this.open; // for TitlePane, etc. + }else{ + // TODO: with _childOfLayoutWidget check maybe this branch no longer necessary? + var node = this.domNode; + return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden"); + } + }, + + _onShow: function(){ + // summary: + // Called when the ContentPane is made visible + // description: + // For a plain ContentPane, this is called on initialization, from startup(). + // If the ContentPane is a hidden pane of a TabContainer etc., then it's + // called whenever the pane is made visible. + // + // Does necessary processing, including href download and layout/resize of + // child widget(s) + + if(this.href){ + if(!this._xhrDfd && // if there's an href that isn't already being loaded + (!this.isLoaded || this._hrefChanged || this.refreshOnShow) + ){ + this.refresh(); + } + }else{ + // If we are the child of a layout widget then the layout widget will call resize() on + // us, and then we will size our child/children. Otherwise, we need to do it now. + if(!this._childOfLayoutWidget && this._needLayout){ + // If a layout has been scheduled for when we become visible, do it now + this._layoutChildren(); + } + } + + this.inherited(arguments); + + // Need to keep track of whether ContentPane has been shown (which is different than + // whether or not it's currently visible). + this._wasShown = true; + }, + + refresh: function(){ + // summary: + // [Re]download contents of href and display + // description: + // 1. cancels any currently in-flight requests + // 2. posts "loading..." message + // 3. sends XHR to download new data + + // Cancel possible prior in-flight request + this.cancel(); + + this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); + this._load(); + return this.onLoadDeferred; + }, + + _load: function(){ + // summary: + // Load/reload the href specified in this.href + + // display loading message + this._setContent(this.onDownloadStart(), true); + + var self = this; + var getArgs = { + preventCache: (this.preventCache || this.refreshOnShow), + url: this.href, + handleAs: "text" + }; + if(dojo.isObject(this.ioArgs)){ + dojo.mixin(getArgs, this.ioArgs); + } + + var hand = (this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs)); + + hand.addCallback(function(html){ + try{ + self._isDownloaded = true; + self._setContent(html, false); + self.onDownloadEnd(); + }catch(err){ + self._onError('Content', err); // onContentError + } + delete self._xhrDfd; + return html; + }); + + hand.addErrback(function(err){ + if(!hand.canceled){ + // show error message in the pane + self._onError('Download', err); // onDownloadError + } + delete self._xhrDfd; + return err; + }); + + // Remove flag saying that a load is needed + delete this._hrefChanged; + }, + + _onLoadHandler: function(data){ + // summary: + // This is called whenever new content is being loaded + this.isLoaded = true; + try{ + this.onLoadDeferred.callback(data); + this.onLoad(data); + }catch(e){ + console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message); + } + }, + + _onUnloadHandler: function(){ + // summary: + // This is called whenever the content is being unloaded + this.isLoaded = false; + try{ + this.onUnload(); + }catch(e){ + console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message); + } + }, + + destroyDescendants: function(){ + // summary: + // Destroy all the widgets inside the ContentPane and empty containerNode + + // Make sure we call onUnload (but only when the ContentPane has real content) + if(this.isLoaded){ + this._onUnloadHandler(); + } + + // Even if this.isLoaded == false there might still be a "Loading..." message + // to erase, so continue... + + // For historical reasons we need to delete all widgets under this.containerNode, + // even ones that the user has created manually. + var setter = this._contentSetter; + dojo.forEach(this.getChildren(), function(widget){ + if(widget.destroyRecursive){ + widget.destroyRecursive(); + } + }); + if(setter){ + // Most of the widgets in setter.parseResults have already been destroyed, but + // things like Menu that have been moved to <body> haven't yet + dojo.forEach(setter.parseResults, function(widget){ + if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == dojo.body()){ + widget.destroyRecursive(); + } + }); + delete setter.parseResults; + } + + // And then clear away all the DOM nodes + dojo.html._emptyNode(this.containerNode); + + // Delete any state information we have about current contents + delete this._singleChild; + }, + + _setContent: function(cont, isFakeContent){ + // summary: + // Insert the content into the container node + + // first get rid of child widgets + this.destroyDescendants(); + + // dojo.html.set will take care of the rest of the details + // we provide an override for the error handling to ensure the widget gets the errors + // configure the setter instance with only the relevant widget instance properties + // NOTE: unless we hook into attr, or provide property setters for each property, + // we need to re-configure the ContentSetter with each use + var setter = this._contentSetter; + if(! (setter && setter instanceof dojo.html._ContentSetter)){ + setter = this._contentSetter = new dojo.html._ContentSetter({ + node: this.containerNode, + _onError: dojo.hitch(this, this._onError), + onContentError: dojo.hitch(this, function(e){ + // fires if a domfault occurs when we are appending this.errorMessage + // like for instance if domNode is a UL and we try append a DIV + var errMess = this.onContentError(e); + try{ + this.containerNode.innerHTML = errMess; + }catch(e){ + console.error('Fatal '+this.id+' could not change content due to '+e.message, e); + } + })/*, + _onError */ + }); + }; + + var setterParams = dojo.mixin({ + cleanContent: this.cleanContent, + extractContent: this.extractContent, + parseContent: this.parseOnLoad, + dir: this.dir, + lang: this.lang + }, this._contentSetterParams || {}); + + dojo.mixin(setter, setterParams); + + setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont ); + + // setter params must be pulled afresh from the ContentPane each time + delete this._contentSetterParams; + + if(!isFakeContent){ + // Startup each top level child widget (and they will start their children, recursively) + dojo.forEach(this.getChildren(), function(child){ + // The parser has already called startup on all widgets *without* a getParent() method + if(!this.parseOnLoad || child.getParent){ + child.startup(); + } + }, this); + + // Call resize() on each of my child layout widgets, + // or resize() on my single child layout widget... + // either now (if I'm currently visible) + // or when I become visible + this._scheduleLayout(); + + this._onLoadHandler(cont); + } + }, + + _onError: function(type, err, consoleText){ + this.onLoadDeferred.errback(err); + + // shows user the string that is returned by on[type]Error + // overide on[type]Error and return your own string to customize + var errText = this['on' + type + 'Error'].call(this, err); + if(consoleText){ + console.error(consoleText, err); + }else if(errText){// a empty string won't change current content + this._setContent(errText, true); + } + }, + + _scheduleLayout: function(){ + // summary: + // Call resize() on each of my child layout widgets, either now + // (if I'm currently visible) or when I become visible + if(this._isShown()){ + this._layoutChildren(); + }else{ + this._needLayout = true; + } + }, + + _layoutChildren: function(){ + // summary: + // Since I am a Container widget, each of my children expects me to + // call resize() or layout() on them. + // description: + // Should be called on initialization and also whenever we get new content + // (from an href, or from attr('content', ...))... but deferred until + // the ContentPane is visible + + if(this.doLayout){ + this._checkIfSingleChild(); + } + + if(this._singleChild && this._singleChild.resize){ + var cb = this._contentBox || dojo.contentBox(this.containerNode); + + // note: if widget has padding this._contentBox will have l and t set, + // but don't pass them to resize() or it will doubly-offset the child + this._singleChild.resize({w: cb.w, h: cb.h}); + }else{ + // All my child widgets are independently sized (rather than matching my size), + // but I still need to call resize() on each child to make it layout. + dojo.forEach(this.getChildren(), function(widget){ + if(widget.resize){ + widget.resize(); + } + }); + } + delete this._needLayout; + }, + + // EVENT's, should be overide-able + onLoad: function(data){ + // summary: + // Event hook, is called after everything is loaded and widgetified + // tags: + // callback + }, + + onUnload: function(){ + // summary: + // Event hook, is called before old content is cleared + // tags: + // callback + }, + + onDownloadStart: function(){ + // summary: + // Called before download starts. + // description: + // The string returned by this function will be the html + // that tells the user we are loading something. + // Override with your own function if you want to change text. + // tags: + // extension + return this.loadingMessage; + }, + + onContentError: function(/*Error*/ error){ + // summary: + // Called on DOM faults, require faults etc. in content. + // + // In order to display an error message in the pane, return + // the error message from this method, as an HTML string. + // + // By default (if this method is not overriden), it returns + // nothing, so the error message is just printed to the console. + // tags: + // extension + }, + + onDownloadError: function(/*Error*/ error){ + // summary: + // Called when download error occurs. + // + // In order to display an error message in the pane, return + // the error message from this method, as an HTML string. + // + // Default behavior (if this method is not overriden) is to display + // the error message inside the pane. + // tags: + // extension + return this.errorMessage; + }, + + onDownloadEnd: function(){ + // summary: + // Called when download is finished. + // tags: + // callback + } +}); + +} + +if(!dojo._hasResource["dijit.TooltipDialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.TooltipDialog"] = true; +dojo.provide("dijit.TooltipDialog"); + + + + + + +dojo.declare( + "dijit.TooltipDialog", + [dijit.layout.ContentPane, dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin], + { + // summary: + // Pops up a dialog that appears like a Tooltip + + // title: String + // Description of tooltip dialog (required for a11y) + title: "", + + // doLayout: [protected] Boolean + // Don't change this parameter from the default value. + // This ContentPane parameter doesn't make sense for TooltipDialog, since TooltipDialog + // is never a child of a layout container, nor can you specify the size of + // TooltipDialog in order to control the size of an inner widget. + doLayout: false, + + // autofocus: Boolean + // A Toggle to modify the default focus behavior of a Dialog, which + // is to focus on the first dialog element after opening the dialog. + // False will disable autofocusing. Default: true + autofocus: true, + + // baseClass: [protected] String + // The root className to use for the various states of this widget + baseClass: "dijitTooltipDialog", + + // _firstFocusItem: [private] [readonly] DomNode + // The pointer to the first focusable node in the dialog. + // Set by `dijit._DialogMixin._getFocusItems`. + _firstFocusItem: null, + + // _lastFocusItem: [private] [readonly] DomNode + // The pointer to which node has focus prior to our dialog. + // Set by `dijit._DialogMixin._getFocusItems`. + _lastFocusItem: null, + + templateString: dojo.cache("dijit", "templates/TooltipDialog.html", "<div waiRole=\"presentation\">\n\t<div class=\"dijitTooltipContainer\" waiRole=\"presentation\">\n\t\t<div class =\"dijitTooltipContents dijitTooltipFocusNode\" dojoAttachPoint=\"containerNode\" tabindex=\"-1\" waiRole=\"dialog\"></div>\n\t</div>\n\t<div class=\"dijitTooltipConnector\" waiRole=\"presentation\"></div>\n</div>\n"), + + postCreate: function(){ + this.inherited(arguments); + this.connect(this.containerNode, "onkeypress", "_onKey"); + this.containerNode.title = this.title; + }, + + orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ corner){ + // summary: + // Configure widget to be displayed in given position relative to the button. + // This is called from the dijit.popup code, and should not be called + // directly. + // tags: + // protected + var c = this._currentOrientClass; + if(c){ + dojo.removeClass(this.domNode, c); + } + c = "dijitTooltipAB"+(corner.charAt(1) == 'L'?"Left":"Right")+" dijitTooltip"+(corner.charAt(0) == 'T' ? "Below" : "Above"); + dojo.addClass(this.domNode, c); + this._currentOrientClass = c; + }, + + onOpen: function(/*Object*/ pos){ + // summary: + // Called when dialog is displayed. + // This is called from the dijit.popup code, and should not be called directly. + // tags: + // protected + + this.orient(this.domNode,pos.aroundCorner, pos.corner); + this._onShow(); // lazy load trigger + + if(this.autofocus){ + this._getFocusItems(this.containerNode); + dijit.focus(this._firstFocusItem); + } + }, + + onClose: function(){ + // summary: + // Called when dialog is hidden. + // This is called from the dijit.popup code, and should not be called directly. + // tags: + // protected + this.onHide(); + }, + + _onKey: function(/*Event*/ evt){ + // summary: + // Handler for keyboard events + // description: + // Keep keyboard focus in dialog; close dialog on escape key + // tags: + // private + + var node = evt.target; + var dk = dojo.keys; + if(evt.charOrCode === dk.TAB){ + this._getFocusItems(this.containerNode); + } + var singleFocusItem = (this._firstFocusItem == this._lastFocusItem); + if(evt.charOrCode == dk.ESCAPE){ + // Use setTimeout to avoid crash on IE, see #10396. + setTimeout(dojo.hitch(this, "onCancel"), 0); + dojo.stopEvent(evt); + }else if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){ + if(!singleFocusItem){ + dijit.focus(this._lastFocusItem); // send focus to last item in dialog + } + dojo.stopEvent(evt); + }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){ + if(!singleFocusItem){ + dijit.focus(this._firstFocusItem); // send focus to first item in dialog + } + dojo.stopEvent(evt); + }else if(evt.charOrCode === dk.TAB){ + // we want the browser's default tab handling to move focus + // but we don't want the tab to propagate upwards + evt.stopPropagation(); + } + } + } + ); + +} + +if(!dojo._hasResource["dijit.Dialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.Dialog"] = true; +dojo.provide("dijit.Dialog"); + + + + + + + + + + + + + + + +/*===== +dijit._underlay = function(kwArgs){ + // summary: + // A shared instance of a `dijit.DialogUnderlay` + // + // description: + // A shared instance of a `dijit.DialogUnderlay` created and + // used by `dijit.Dialog`, though never created until some Dialog + // or subclass thereof is shown. +}; +=====*/ + +dojo.declare( + "dijit._DialogBase", + [dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin, dijit._CssStateMixin], + { + // summary: + // A modal dialog Widget + // + // description: + // Pops up a modal dialog window, blocking access to the screen + // and also graying out the screen Dialog is extended from + // ContentPane so it supports all the same parameters (href, etc.) + // + // example: + // | <div dojoType="dijit.Dialog" href="test.html"></div> + // + // example: + // | var foo = new dijit.Dialog({ title: "test dialog", content: "test content" }; + // | dojo.body().appendChild(foo.domNode); + // | foo.startup(); + + templateString: dojo.cache("dijit", "templates/Dialog.html", "<div class=\"dijitDialog\" tabindex=\"-1\" waiRole=\"dialog\" waiState=\"labelledby-${id}_title\">\n\t<div dojoAttachPoint=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span dojoAttachPoint=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span dojoAttachPoint=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" dojoAttachEvent=\"onclick: onCancel\" title=\"${buttonCancel}\">\n\t\t<span dojoAttachPoint=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div dojoAttachPoint=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"), + + baseClass: "dijitDialog", + + cssStateNodes: { + closeButtonNode: "dijitDialogCloseIcon" + }, + + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + title: [ + { node: "titleNode", type: "innerHTML" }, + { node: "titleBar", type: "attribute" } + ], + "aria-describedby":"" + }), + + // open: Boolean + // True if Dialog is currently displayed on screen. + open: false, + + // duration: Integer + // The time in milliseconds it takes the dialog to fade in and out + duration: dijit.defaultDuration, + + // refocus: Boolean + // A Toggle to modify the default focus behavior of a Dialog, which + // is to re-focus the element which had focus before being opened. + // False will disable refocusing. Default: true + refocus: true, + + // autofocus: Boolean + // A Toggle to modify the default focus behavior of a Dialog, which + // is to focus on the first dialog element after opening the dialog. + // False will disable autofocusing. Default: true + autofocus: true, + + // _firstFocusItem: [private] [readonly] DomNode + // The pointer to the first focusable node in the dialog. + // Set by `dijit._DialogMixin._getFocusItems`. + _firstFocusItem: null, + + // _lastFocusItem: [private] [readonly] DomNode + // The pointer to which node has focus prior to our dialog. + // Set by `dijit._DialogMixin._getFocusItems`. + _lastFocusItem: null, + + // doLayout: [protected] Boolean + // Don't change this parameter from the default value. + // This ContentPane parameter doesn't make sense for Dialog, since Dialog + // is never a child of a layout container, nor can you specify the size of + // Dialog in order to control the size of an inner widget. + doLayout: false, + + // draggable: Boolean + // Toggles the moveable aspect of the Dialog. If true, Dialog + // can be dragged by it's title. If false it will remain centered + // in the viewport. + draggable: true, + + //aria-describedby: String + // Allows the user to add an aria-describedby attribute onto the dialog. The value should + // be the id of the container element of text that describes the dialog purpose (usually + // the first text in the dialog). + // <div dojoType="dijit.Dialog" aria-describedby="intro" .....> + // <div id="intro">Introductory text</div> + // <div>rest of dialog contents</div> + // </div> + "aria-describedby":"", + + postMixInProperties: function(){ + var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); + dojo.mixin(this, _nlsResources); + this.inherited(arguments); + }, + + postCreate: function(){ + dojo.style(this.domNode, { + display: "none", + position:"absolute" + }); + dojo.body().appendChild(this.domNode); + + this.inherited(arguments); + + this.connect(this, "onExecute", "hide"); + this.connect(this, "onCancel", "hide"); + this._modalconnects = []; + }, + + onLoad: function(){ + // summary: + // Called when data has been loaded from an href. + // Unlike most other callbacks, this function can be connected to (via `dojo.connect`) + // but should *not* be overriden. + // tags: + // callback + + // when href is specified we need to reposition the dialog after the data is loaded + // and find the focusable elements + this._position(); + if(this.autofocus){ + this._getFocusItems(this.domNode); + dijit.focus(this._firstFocusItem); + } + this.inherited(arguments); + }, + + _endDrag: function(e){ + // summary: + // Called after dragging the Dialog. Saves the position of the dialog in the viewport. + // tags: + // private + if(e && e.node && e.node === this.domNode){ + this._relativePosition = dojo.position(e.node); + } + }, + + _setup: function(){ + // summary: + // Stuff we need to do before showing the Dialog for the first + // time (but we defer it until right beforehand, for + // performance reasons). + // tags: + // private + + var node = this.domNode; + + if(this.titleBar && this.draggable){ + this._moveable = (dojo.isIE == 6) ? + new dojo.dnd.TimedMoveable(node, { handle: this.titleBar }) : // prevent overload, see #5285 + new dojo.dnd.Moveable(node, { handle: this.titleBar, timeout: 0 }); + dojo.subscribe("/dnd/move/stop",this,"_endDrag"); + }else{ + dojo.addClass(node,"dijitDialogFixed"); + } + + this.underlayAttrs = { + dialogId: this.id, + "class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ") + }; + + this._fadeIn = dojo.fadeIn({ + node: node, + duration: this.duration, + beforeBegin: dojo.hitch(this, function(){ + var underlay = dijit._underlay; + if(!underlay){ + underlay = dijit._underlay = new dijit.DialogUnderlay(this.underlayAttrs); + }else{ + underlay.set(this.underlayAttrs); + } + + var ds = dijit._dialogStack, + zIndex = 948 + ds.length*2; + if(ds.length == 1){ // first dialog + underlay.show(); + } + dojo.style(dijit._underlay.domNode, 'zIndex', zIndex); + dojo.style(this.domNode, 'zIndex', zIndex + 1); + }), + onEnd: dojo.hitch(this, function(){ + if(this.autofocus){ + // find focusable Items each time dialog is shown since if dialog contains a widget the + // first focusable items can change + this._getFocusItems(this.domNode); + dijit.focus(this._firstFocusItem); + } + }) + }); + + this._fadeOut = dojo.fadeOut({ + node: node, + duration: this.duration, + onEnd: dojo.hitch(this, function(){ + node.style.display = "none"; + + // Restore the previous dialog in the stack, or if this is the only dialog + // then restore to original page + var ds = dijit._dialogStack; + if(ds.length == 0){ + dijit._underlay.hide(); + }else{ + dojo.style(dijit._underlay.domNode, 'zIndex', 948 + ds.length*2); + dijit._underlay.set(ds[ds.length-1].underlayAttrs); + } + + // Restore focus to wherever it was before this dialog was displayed + if(this.refocus){ + var focus = this._savedFocus; + + // If we are returning control to a previous dialog but for some reason + // that dialog didn't have a focused field, set focus to first focusable item. + // This situation could happen if two dialogs appeared at nearly the same time, + // since a dialog doesn't set it's focus until the fade-in is finished. + if(ds.length > 0){ + var pd = ds[ds.length-1]; + if(!dojo.isDescendant(focus.node, pd.domNode)){ + pd._getFocusItems(pd.domNode); + focus = pd._firstFocusItem; + } + } + + dijit.focus(focus); + } + }) + }); + }, + + uninitialize: function(){ + var wasPlaying = false; + if(this._fadeIn && this._fadeIn.status() == "playing"){ + wasPlaying = true; + this._fadeIn.stop(); + } + if(this._fadeOut && this._fadeOut.status() == "playing"){ + wasPlaying = true; + this._fadeOut.stop(); + } + + // Hide the underlay, unless the underlay widget has already been destroyed + // because we are being called during page unload (when all widgets are destroyed) + if((this.open || wasPlaying) && !dijit._underlay._destroyed){ + dijit._underlay.hide(); + } + + if(this._moveable){ + this._moveable.destroy(); + } + this.inherited(arguments); + }, + + _size: function(){ + // summary: + // If necessary, shrink dialog contents so dialog fits in viewport + // tags: + // private + + this._checkIfSingleChild(); + + // If we resized the dialog contents earlier, reset them back to original size, so + // that if the user later increases the viewport size, the dialog can display w/out a scrollbar. + // Need to do this before the dojo.marginBox(this.domNode) call below. + if(this._singleChild){ + if(this._singleChildOriginalStyle){ + this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle; + } + delete this._singleChildOriginalStyle; + }else{ + dojo.style(this.containerNode, { + width:"auto", + height:"auto" + }); + } + + var mb = dojo.marginBox(this.domNode); + var viewport = dojo.window.getBox(); + if(mb.w >= viewport.w || mb.h >= viewport.h){ + // Reduce size of dialog contents so that dialog fits in viewport + + var w = Math.min(mb.w, Math.floor(viewport.w * 0.75)), + h = Math.min(mb.h, Math.floor(viewport.h * 0.75)); + + if(this._singleChild && this._singleChild.resize){ + this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText; + this._singleChild.resize({w: w, h: h}); + }else{ + dojo.style(this.containerNode, { + width: w + "px", + height: h + "px", + overflow: "auto", + position: "relative" // workaround IE bug moving scrollbar or dragging dialog + }); + } + }else{ + if(this._singleChild && this._singleChild.resize){ + this._singleChild.resize(); + } + } + }, + + _position: function(){ + // summary: + // Position modal dialog in the viewport. If no relative offset + // in the viewport has been determined (by dragging, for instance), + // center the node. Otherwise, use the Dialog's stored relative offset, + // and position the node to top: left: values based on the viewport. + // tags: + // private + if(!dojo.hasClass(dojo.body(),"dojoMove")){ + var node = this.domNode, + viewport = dojo.window.getBox(), + p = this._relativePosition, + bb = p ? null : dojo._getBorderBox(node), + l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)), + t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2)) + ; + dojo.style(node,{ + left: l + "px", + top: t + "px" + }); + } + }, + + _onKey: function(/*Event*/ evt){ + // summary: + // Handles the keyboard events for accessibility reasons + // tags: + // private + + var ds = dijit._dialogStack; + if(ds[ds.length-1] != this){ + // console.debug(this.id + ': skipping because', this, 'is not the active dialog'); + return; + } + + if(evt.charOrCode){ + var dk = dojo.keys; + var node = evt.target; + if(evt.charOrCode === dk.TAB){ + this._getFocusItems(this.domNode); + } + var singleFocusItem = (this._firstFocusItem == this._lastFocusItem); + // see if we are shift-tabbing from first focusable item on dialog + if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){ + if(!singleFocusItem){ + dijit.focus(this._lastFocusItem); // send focus to last item in dialog + } + dojo.stopEvent(evt); + }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){ + if(!singleFocusItem){ + dijit.focus(this._firstFocusItem); // send focus to first item in dialog + } + dojo.stopEvent(evt); + }else{ + // see if the key is for the dialog + while(node){ + if(node == this.domNode || dojo.hasClass(node, "dijitPopup")){ + if(evt.charOrCode == dk.ESCAPE){ + this.onCancel(); + }else{ + return; // just let it go + } + } + node = node.parentNode; + } + // this key is for the disabled document window + if(evt.charOrCode !== dk.TAB){ // allow tabbing into the dialog for a11y + dojo.stopEvent(evt); + // opera won't tab to a div + }else if(!dojo.isOpera){ + try{ + this._firstFocusItem.focus(); + }catch(e){ /*squelch*/ } + } + } + } + }, + + show: function(){ + // summary: + // Display the dialog + if(this.open){ return; } + + // first time we show the dialog, there's some initialization stuff to do + if(!this._alreadyInitialized){ + this._setup(); + this._alreadyInitialized=true; + } + + if(this._fadeOut.status() == "playing"){ + this._fadeOut.stop(); + } + + this._modalconnects.push(dojo.connect(window, "onscroll", this, "layout")); + this._modalconnects.push(dojo.connect(window, "onresize", this, function(){ + // IE gives spurious resize events and can actually get stuck + // in an infinite loop if we don't ignore them + var viewport = dojo.window.getBox(); + if(!this._oldViewport || + viewport.h != this._oldViewport.h || + viewport.w != this._oldViewport.w){ + this.layout(); + this._oldViewport = viewport; + } + })); + this._modalconnects.push(dojo.connect(dojo.doc.documentElement, "onkeypress", this, "_onKey")); + + dojo.style(this.domNode, { + opacity:0, + display:"" + }); + + this.open = true; + this._onShow(); // lazy load trigger + + this._size(); + this._position(); + dijit._dialogStack.push(this); + this._fadeIn.play(); + + this._savedFocus = dijit.getFocus(this); + }, + + hide: function(){ + // summary: + // Hide the dialog + + // if we haven't been initialized yet then we aren't showing and we can just return + // or if we aren't the active dialog, don't allow us to close yet + var ds = dijit._dialogStack; + if(!this._alreadyInitialized || this != ds[ds.length-1]){ + return; + } + + if(this._fadeIn.status() == "playing"){ + this._fadeIn.stop(); + } + + // throw away current active dialog from stack -- making the previous dialog or the node on the original page active + ds.pop(); + + this._fadeOut.play(); + + if(this._scrollConnected){ + this._scrollConnected = false; + } + dojo.forEach(this._modalconnects, dojo.disconnect); + this._modalconnects = []; + + if(this._relativePosition){ + delete this._relativePosition; + } + this.open = false; + + this.onHide(); + }, + + layout: function(){ + // summary: + // Position the Dialog and the underlay + // tags: + // private + if(this.domNode.style.display != "none"){ + if(dijit._underlay){ // avoid race condition during show() + dijit._underlay.layout(); + } + this._position(); + } + }, + + destroy: function(){ + dojo.forEach(this._modalconnects, dojo.disconnect); + if(this.refocus && this.open){ + setTimeout(dojo.hitch(dijit,"focus",this._savedFocus), 25); + } + this.inherited(arguments); + } + } +); + +dojo.declare( + "dijit.Dialog", + [dijit.layout.ContentPane, dijit._DialogBase], + {} +); + +// Stack of currenctly displayed dialogs, layered on top of each other +dijit._dialogStack = []; + +// For back-compat. TODO: remove in 2.0 + + +} + +if(!dojo._hasResource["dijit._HasDropDown"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._HasDropDown"] = true; +dojo.provide("dijit._HasDropDown"); + + + + +dojo.declare("dijit._HasDropDown", + null, + { + // summary: + // Mixin for widgets that need drop down ability. + + // _buttonNode: [protected] DomNode + // The button/icon/node to click to display the drop down. + // Can be set via a dojoAttachPoint assignment. + // If missing, then either focusNode or domNode (if focusNode is also missing) will be used. + _buttonNode: null, + + // _arrowWrapperNode: [protected] DomNode + // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending + // on where the drop down is set to be positioned. + // Can be set via a dojoAttachPoint assignment. + // If missing, then _buttonNode will be used. + _arrowWrapperNode: null, + + // _popupStateNode: [protected] DomNode + // The node to set the popupActive class on. + // Can be set via a dojoAttachPoint assignment. + // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used. + _popupStateNode: null, + + // _aroundNode: [protected] DomNode + // The node to display the popup around. + // Can be set via a dojoAttachPoint assignment. + // If missing, then domNode will be used. + _aroundNode: null, + + // dropDown: [protected] Widget + // The widget to display as a popup. This widget *must* be + // defined before the startup function is called. + dropDown: null, + + // autoWidth: [protected] Boolean + // Set to true to make the drop down at least as wide as this + // widget. Set to false if the drop down should just be its + // default width + autoWidth: true, + + // forceWidth: [protected] Boolean + // Set to true to make the drop down exactly as wide as this + // widget. Overrides autoWidth. + forceWidth: false, + + // maxHeight: [protected] Integer + // The max height for our dropdown. Set to 0 for no max height. + // any dropdown taller than this will have scrollbars + maxHeight: 0, + + // dropDownPosition: [const] String[] + // This variable controls the position of the drop down. + // It's an array of strings with the following values: + // + // * before: places drop down to the left of the target node/widget, or to the right in + // the case of RTL scripts like Hebrew and Arabic + // * after: places drop down to the right of the target node/widget, or to the left in + // the case of RTL scripts like Hebrew and Arabic + // * above: drop down goes above target node + // * below: drop down goes below target node + // + // The list is positions is tried, in order, until a position is found where the drop down fits + // within the viewport. + // + dropDownPosition: ["below","above"], + + // _stopClickEvents: Boolean + // When set to false, the click events will not be stopped, in + // case you want to use them in your subwidget + _stopClickEvents: true, + + _onDropDownMouseDown: function(/*Event*/ e){ + // summary: + // Callback when the user mousedown's on the arrow icon + + if(this.disabled || this.readOnly){ return; } + + this._docHandler = this.connect(dojo.doc, "onmouseup", "_onDropDownMouseUp"); + + this.toggleDropDown(); + }, + + _onDropDownMouseUp: function(/*Event?*/ e){ + // summary: + // Callback when the user lifts their mouse after mouse down on the arrow icon. + // If the drop is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our + // dropDown node. If the event is missing, then we are not + // a mouseup event. + // + // This is useful for the common mouse movement pattern + // with native browser <select> nodes: + // 1. mouse down on the select node (probably on the arrow) + // 2. move mouse to a menu item while holding down the mouse button + // 3. mouse up. this selects the menu item as though the user had clicked it. + if(e && this._docHandler){ + this.disconnect(this._docHandler); + } + var dropDown = this.dropDown, overMenu = false; + + if(e && this._opened){ + // This code deals with the corner-case when the drop down covers the original widget, + // because it's so large. In that case mouse-up shouldn't select a value from the menu. + // Find out if our target is somewhere in our dropdown widget, + // but not over our _buttonNode (the clickable node) + var c = dojo.position(this._buttonNode, true); + if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) || + !(e.pageY >= c.y && e.pageY <= c.y + c.h)){ + var t = e.target; + while(t && !overMenu){ + if(dojo.hasClass(t, "dijitPopup")){ + overMenu = true; + }else{ + t = t.parentNode; + } + } + if(overMenu){ + t = e.target; + if(dropDown.onItemClick){ + var menuItem; + while(t && !(menuItem = dijit.byNode(t))){ + t = t.parentNode; + } + if(menuItem && menuItem.onClick && menuItem.getParent){ + menuItem.getParent().onItemClick(menuItem, e); + } + } + return; + } + } + } + if(this._opened && dropDown.focus){ + // Focus the dropdown widget - do it on a delay so that we + // don't steal our own focus. + window.setTimeout(dojo.hitch(dropDown, "focus"), 1); + } + }, + + _onDropDownClick: function(/*Event*/ e){ + // the drop down was already opened on mousedown/keydown; just need to call stopEvent() + if(this._stopClickEvents){ + dojo.stopEvent(e); + } + }, + + _setupDropdown: function(){ + // summary: + // set up nodes and connect our mouse and keypress events + this._buttonNode = this._buttonNode || this.focusNode || this.domNode; + this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode; + this._aroundNode = this._aroundNode || this.domNode; + this.connect(this._buttonNode, "onmousedown", "_onDropDownMouseDown"); + this.connect(this._buttonNode, "onclick", "_onDropDownClick"); + this.connect(this._buttonNode, "onkeydown", "_onDropDownKeydown"); + this.connect(this._buttonNode, "onkeyup", "_onKey"); + + // If we have a _setStateClass function (which happens when + // we are a form widget), then we need to connect our open/close + // functions to it + if(this._setStateClass){ + this.connect(this, "openDropDown", "_setStateClass"); + this.connect(this, "closeDropDown", "_setStateClass"); + } + + // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow + // based on where drop down will normally appear + var defaultPos = { + "after" : this.isLeftToRight() ? "Right" : "Left", + "before" : this.isLeftToRight() ? "Left" : "Right", + "above" : "Up", + "below" : "Down", + "left" : "Left", + "right" : "Right" + }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down"; + dojo.addClass(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton"); + }, + + postCreate: function(){ + this._setupDropdown(); + this.inherited(arguments); + }, + + destroyDescendants: function(){ + if(this.dropDown){ + // Destroy the drop down, unless it's already been destroyed. This can happen because + // the drop down is a direct child of <body> even though it's logically my child. + if(!this.dropDown._destroyed){ + this.dropDown.destroyRecursive(); + } + delete this.dropDown; + } + this.inherited(arguments); + }, + + _onDropDownKeydown: function(/*Event*/ e){ + if(e.keyCode == dojo.keys.DOWN_ARROW || e.keyCode == dojo.keys.ENTER || e.keyCode == dojo.keys.SPACE){ + e.preventDefault(); // stop IE screen jump + } + }, + + _onKey: function(/*Event*/ e){ + // summary: + // Callback when the user presses a key while focused on the button node + + if(this.disabled || this.readOnly){ return; } + var d = this.dropDown; + if(d && this._opened && d.handleKey){ + if(d.handleKey(e) === false){ return; } + } + if(d && this._opened && e.keyCode == dojo.keys.ESCAPE){ + this.toggleDropDown(); + }else if(d && !this._opened && + (e.keyCode == dojo.keys.DOWN_ARROW || e.keyCode == dojo.keys.ENTER || e.keyCode == dojo.keys.SPACE)){ + this.toggleDropDown(); + if(d.focus){ + setTimeout(dojo.hitch(d, "focus"), 1); + } + } + }, + + _onBlur: function(){ + // summary: + // Called magically when focus has shifted away from this widget and it's dropdown + + this.closeDropDown(); + // don't focus on button. the user has explicitly focused on something else. + this.inherited(arguments); + }, + + isLoaded: function(){ + // summary: + // Returns whether or not the dropdown is loaded. This can + // be overridden in order to force a call to loadDropDown(). + // tags: + // protected + + return true; + }, + + loadDropDown: function(/* Function */ loadCallback){ + // summary: + // Loads the data for the dropdown, and at some point, calls + // the given callback + // tags: + // protected + + loadCallback(); + }, + + toggleDropDown: function(){ + // summary: + // Toggle the drop-down widget; if it is up, close it, if not, open it + // tags: + // protected + + if(this.disabled || this.readOnly){ return; } + this.focus(); + var dropDown = this.dropDown; + if(!dropDown){ return; } + if(!this._opened){ + // If we aren't loaded, load it first so there isn't a flicker + if(!this.isLoaded()){ + this.loadDropDown(dojo.hitch(this, "openDropDown")); + return; + }else{ + this.openDropDown(); + } + }else{ + this.closeDropDown(); + } + }, + + openDropDown: function(){ + // summary: + // Opens the dropdown for this widget - it returns the + // return value of dijit.popup.open + // tags: + // protected + + var dropDown = this.dropDown; + var ddNode = dropDown.domNode; + var self = this; + + // Prepare our popup's height and honor maxHeight if it exists. + + // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(), + // ie, dependent on how much space is available (BK) + + if(!this._preparedNode){ + dijit.popup.moveOffScreen(ddNode); + this._preparedNode = true; + // Check if we have explicitly set width and height on the dropdown widget dom node + if(ddNode.style.width){ + this._explicitDDWidth = true; + } + if(ddNode.style.height){ + this._explicitDDHeight = true; + } + } + + // Code for resizing dropdown (height limitation, or increasing width to match my width) + if(this.maxHeight || this.forceWidth || this.autoWidth){ + var myStyle = { + display: "", + visibility: "hidden" + }; + if(!this._explicitDDWidth){ + myStyle.width = ""; + } + if(!this._explicitDDHeight){ + myStyle.height = ""; + } + dojo.style(ddNode, myStyle); + + // Get size of drop down, and determine if vertical scroll bar needed + var mb = dojo.marginBox(ddNode); + var overHeight = (this.maxHeight && mb.h > this.maxHeight); + dojo.style(ddNode, { + overflowX: "hidden", + overflowY: overHeight ? "auto" : "hidden" + }); + if(overHeight){ + mb.h = this.maxHeight; + if("w" in mb){ + mb.w += 16; // room for vertical scrollbar + } + }else{ + delete mb.h; + } + delete mb.t; + delete mb.l; + + // Adjust dropdown width to match or be larger than my width + if(this.forceWidth){ + mb.w = this.domNode.offsetWidth; + }else if(this.autoWidth){ + mb.w = Math.max(mb.w, this.domNode.offsetWidth); + }else{ + delete mb.w; + } + + // And finally, resize the dropdown to calculated height and width + if(dojo.isFunction(dropDown.resize)){ + dropDown.resize(mb); + }else{ + dojo.marginBox(ddNode, mb); + } + } + + var retVal = dijit.popup.open({ + parent: this, + popup: dropDown, + around: this._aroundNode, + orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below"],this.isLeftToRight()), + onExecute: function(){ + self.closeDropDown(true); + }, + onCancel: function(){ + self.closeDropDown(true); + }, + onClose: function(){ + dojo.attr(self._popupStateNode, "popupActive", false); + dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen"); + self._opened = false; + self.state = ""; + } + }); + dojo.attr(this._popupStateNode, "popupActive", "true"); + dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen"); + this._opened=true; + this.state="Opened"; + // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown + return retVal; + }, + + closeDropDown: function(/*Boolean*/ focus){ + // summary: + // Closes the drop down on this widget + // tags: + // protected + + if(this._opened){ + if(focus){ this.focus(); } + dijit.popup.close(this.dropDown); + this._opened = false; + this.state = ""; + } + } + + } +); + +} + +if(!dojo._hasResource["dijit.form.Button"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.Button"] = true; +dojo.provide("dijit.form.Button"); + + + + + +dojo.declare("dijit.form.Button", + dijit.form._FormWidget, + { + // summary: + // Basically the same thing as a normal HTML button, but with special styling. + // description: + // Buttons can display a label, an icon, or both. + // A label should always be specified (through innerHTML) or the label + // attribute. It can be hidden via showLabel=false. + // example: + // | <button dojoType="dijit.form.Button" onClick="...">Hello world</button> + // + // example: + // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo}); + // | dojo.body().appendChild(button1.domNode); + + // label: HTML String + // Text to display in button. + // If the label is hidden (showLabel=false) then and no title has + // been specified, then label is also set as title attribute of icon. + label: "", + + // showLabel: Boolean + // Set this to true to hide the label text and display only the icon. + // (If showLabel=false then iconClass must be specified.) + // Especially useful for toolbars. + // If showLabel=true, the label will become the title (a.k.a. tooltip/hint) of the icon. + // + // The exception case is for computers in high-contrast mode, where the label + // will still be displayed, since the icon doesn't appear. + showLabel: true, + + // iconClass: String + // Class to apply to DOMNode in button to make it display an icon + iconClass: "", + + // type: String + // Defines the type of button. "button", "submit", or "reset". + type: "button", + + baseClass: "dijitButton", + + templateString: dojo.cache("dijit.form", "templates/Button.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">●</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"), + + attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { + value: "valueNode", + iconClass: { node: "iconNode", type: "class" } + }), + + + _onClick: function(/*Event*/ e){ + // summary: + // Internal function to handle click actions + if(this.disabled){ + return false; + } + this._clicked(); // widget click actions + return this.onClick(e); // user click actions + }, + + _onButtonClick: function(/*Event*/ e){ + // summary: + // Handler when the user activates the button portion. + if(this._onClick(e) === false){ // returning nothing is same as true + e.preventDefault(); // needed for checkbox + }else if(this.type == "submit" && !(this.valueNode||this.focusNode).form){ // see if a nonform widget needs to be signalled + for(var node=this.domNode; node.parentNode/*#5935*/; node=node.parentNode){ + var widget=dijit.byNode(node); + if(widget && typeof widget._onSubmit == "function"){ + widget._onSubmit(e); + break; + } + } + }else if(this.valueNode){ + this.valueNode.click(); + e.preventDefault(); // cancel BUTTON click and continue with hidden INPUT click + } + }, + + _fillContent: function(/*DomNode*/ source){ + // Overrides _Templated._fillContent(). + // If button label is specified as srcNodeRef.innerHTML rather than + // this.params.label, handle it here. + if(source && (!this.params || !("label" in this.params))){ + this.set('label', source.innerHTML); + } + }, + + postCreate: function(){ + dojo.setSelectable(this.focusNode, false); + this.inherited(arguments); + }, + + _setShowLabelAttr: function(val){ + if(this.containerNode){ + dojo.toggleClass(this.containerNode, "dijitDisplayNone", !val); + } + this.showLabel = val; + }, + + onClick: function(/*Event*/ e){ + // summary: + // Callback for when button is clicked. + // If type="submit", return true to perform submit, or false to cancel it. + // type: + // callback + return true; // Boolean + }, + + _clicked: function(/*Event*/ e){ + // summary: + // Internal overridable function for when the button is clicked + }, + + setLabel: function(/*String*/ content){ + // summary: + // Deprecated. Use set('label', ...) instead. + dojo.deprecated("dijit.form.Button.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0"); + this.set("label", content); + }, + + _setLabelAttr: function(/*String*/ content){ + // summary: + // Hook for attr('label', ...) to work. + // description: + // Set the label (text) of the button; takes an HTML string. + this.containerNode.innerHTML = this.label = content; + if(this.showLabel == false && !this.params.title){ + this.titleNode.title = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || ''); + } + } +}); + + +dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container, dijit._HasDropDown], { + // summary: + // A button with a drop down + // + // example: + // | <button dojoType="dijit.form.DropDownButton" label="Hello world"> + // | <div dojotype="dijit.Menu">...</div> + // | </button> + // + // example: + // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) }); + // | dojo.body().appendChild(button1); + // + + baseClass : "dijitDropDownButton", + + templateString: dojo.cache("dijit.form", "templates/DropDownButton.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\" dojoAttachPoint=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true,labelledby-${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">▼</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"), + + _fillContent: function(){ + // Overrides Button._fillContent(). + // + // My inner HTML contains both the button contents and a drop down widget, like + // <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton> + // The first node is assumed to be the button content. The widget is the popup. + + if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef + //FIXME: figure out how to filter out the widget and use all remaining nodes as button + // content, not just nodes[0] + var nodes = dojo.query("*", this.srcNodeRef); + dijit.form.DropDownButton.superclass._fillContent.call(this, nodes[0]); + + // save pointer to srcNode so we can grab the drop down widget after it's instantiated + this.dropDownContainer = this.srcNodeRef; + } + }, + + startup: function(){ + if(this._started){ return; } + + // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM, + // make it invisible, and store a reference to pass to the popup code. + if(!this.dropDown){ + var dropDownNode = dojo.query("[widgetId]", this.dropDownContainer)[0]; + this.dropDown = dijit.byNode(dropDownNode); + delete this.dropDownContainer; + } + dijit.popup.moveOffScreen(this.dropDown.domNode); + + this.inherited(arguments); + }, + + isLoaded: function(){ + // Returns whether or not we are loaded - if our dropdown has an href, + // then we want to check that. + var dropDown = this.dropDown; + return (!dropDown.href || dropDown.isLoaded); + }, + + loadDropDown: function(){ + // Loads our dropdown + var dropDown = this.dropDown; + if(!dropDown){ return; } + if(!this.isLoaded()){ + var handler = dojo.connect(dropDown, "onLoad", this, function(){ + dojo.disconnect(handler); + this.openDropDown(); + }); + dropDown.refresh(); + }else{ + this.openDropDown(); + } + }, + + isFocusable: function(){ + // Overridden so that focus is handled by the _HasDropDown mixin, not by + // the _FormWidget mixin. + return this.inherited(arguments) && !this._mouseDown; + } +}); + +dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, { + // summary: + // A combination button and drop-down button. + // Users can click one side to "press" the button, or click an arrow + // icon to display the drop down. + // + // example: + // | <button dojoType="dijit.form.ComboButton" onClick="..."> + // | <span>Hello world</span> + // | <div dojoType="dijit.Menu">...</div> + // | </button> + // + // example: + // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"}); + // | dojo.body().appendChild(button1.domNode); + // + + templateString: dojo.cache("dijit.form", "templates/ComboButton.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tcellspacing='0' cellpadding='0' waiRole=\"presentation\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\" dojoAttachPoint=\"buttonNode\" dojoAttachEvent=\"ondijitclick:_onButtonClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" waiRole=\"presentation\"></div\n\t\t></div\n\t\t></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onArrowKeyPress\"\n\t\t\ttitle=\"${optionsTitle}\"\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">▼</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"), + + attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), { + id: "", + tabIndex: ["focusNode", "titleNode"], + title: "titleNode" + }), + + // optionsTitle: String + // Text that describes the options menu (accessibility) + optionsTitle: "", + + baseClass: "dijitComboButton", + + // Set classes like dijitButtonContentsHover or dijitArrowButtonActive depending on + // mouse action over specified node + cssStateNodes: { + "buttonNode": "dijitButtonNode", + "titleNode": "dijitButtonContents", + "_popupStateNode": "dijitDownArrowButton" + }, + + _focusedNode: null, + + _onButtonKeyPress: function(/*Event*/ evt){ + // summary: + // Handler for right arrow key when focus is on left part of button + if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "RIGHT_ARROW" : "LEFT_ARROW"]){ + dijit.focus(this._popupStateNode); + dojo.stopEvent(evt); + } + }, + + _onArrowKeyPress: function(/*Event*/ evt){ + // summary: + // Handler for left arrow key when focus is on right part of button + if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "LEFT_ARROW" : "RIGHT_ARROW"]){ + dijit.focus(this.titleNode); + dojo.stopEvent(evt); + } + }, + + focus: function(/*String*/ position){ + // summary: + // Focuses this widget to according to position, if specified, + // otherwise on arrow node + // position: + // "start" or "end" + + dijit.focus(position == "start" ? this.titleNode : this._popupStateNode); + } +}); + +dojo.declare("dijit.form.ToggleButton", dijit.form.Button, { + // summary: + // A button that can be in two states (checked or not). + // Can be base class for things like tabs or checkbox or radio buttons + + baseClass: "dijitToggleButton", + + // checked: Boolean + // Corresponds to the native HTML <input> element's attribute. + // In markup, specified as "checked='checked'" or just "checked". + // True if the button is depressed, or the checkbox is checked, + // or the radio button is selected, etc. + checked: false, + + attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), { + checked:"focusNode" + }), + + _clicked: function(/*Event*/ evt){ + this.set('checked', !this.checked); + }, + + _setCheckedAttr: function(/*Boolean*/ value, /* Boolean? */ priorityChange){ + this.checked = value; + dojo.attr(this.focusNode || this.domNode, "checked", value); + dijit.setWaiState(this.focusNode || this.domNode, "pressed", value); + this._handleOnChange(value, priorityChange); + }, + + setChecked: function(/*Boolean*/ checked){ + // summary: + // Deprecated. Use set('checked', true/false) instead. + dojo.deprecated("setChecked("+checked+") is deprecated. Use set('checked',"+checked+") instead.", "", "2.0"); + this.set('checked', checked); + }, + + reset: function(){ + // summary: + // Reset the widget's value to what it was at initialization time + + this._hasBeenBlurred = false; + + // set checked state to original setting + this.set('checked', this.params.checked || false); + } +}); + +} + +if(!dojo._hasResource["dijit.form.ToggleButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.ToggleButton"] = true; +dojo.provide("dijit.form.ToggleButton"); + + +} + +if(!dojo._hasResource["dijit.form.CheckBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.CheckBox"] = true; +dojo.provide("dijit.form.CheckBox"); + + + +dojo.declare( + "dijit.form.CheckBox", + dijit.form.ToggleButton, + { + // summary: + // Same as an HTML checkbox, but with fancy styling. + // + // description: + // User interacts with real html inputs. + // On onclick (which occurs by mouse click, space-bar, or + // using the arrow keys to switch the selected radio button), + // we update the state of the checkbox/radio. + // + // There are two modes: + // 1. High contrast mode + // 2. Normal mode + // + // In case 1, the regular html inputs are shown and used by the user. + // In case 2, the regular html inputs are invisible but still used by + // the user. They are turned quasi-invisible and overlay the background-image. + + templateString: dojo.cache("dijit.form", "templates/CheckBox.html", "<div class=\"dijit dijitReset dijitInline\" waiRole=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onclick:_onClick\"\n/></div>\n"), + + baseClass: "dijitCheckBox", + + // type: [private] String + // type attribute on <input> node. + // Overrides `dijit.form.Button.type`. Users should not change this value. + type: "checkbox", + + // value: String + // As an initialization parameter, equivalent to value field on normal checkbox + // (if checked, the value is passed as the value when form is submitted). + // + // However, attr('value') will return either the string or false depending on + // whether or not the checkbox is checked. + // + // attr('value', string) will check the checkbox and change the value to the + // specified string + // + // attr('value', boolean) will change the checked state. + value: "on", + + // readOnly: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "readOnly". + // Similar to disabled except readOnly form values are submitted. + readOnly: false, + + // the attributeMap should inherit from dijit.form._FormWidget.prototype.attributeMap + // instead of ToggleButton as the icon mapping has no meaning for a CheckBox + attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { + readOnly: "focusNode" + }), + + _setReadOnlyAttr: function(/*Boolean*/ value){ + this.readOnly = value; + dojo.attr(this.focusNode, 'readOnly', value); + dijit.setWaiState(this.focusNode, "readonly", value); + }, + + _setValueAttr: function(/*String or Boolean*/ newValue, /*Boolean*/ priorityChange){ + // summary: + // Handler for value= attribute to constructor, and also calls to + // attr('value', val). + // description: + // During initialization, just saves as attribute to the <input type=checkbox>. + // + // After initialization, + // when passed a boolean, controls whether or not the CheckBox is checked. + // If passed a string, changes the value attribute of the CheckBox (the one + // specified as "value" when the CheckBox was constructed (ex: <input + // dojoType="dijit.CheckBox" value="chicken">) + if(typeof newValue == "string"){ + this.value = newValue; + dojo.attr(this.focusNode, 'value', newValue); + newValue = true; + } + if(this._created){ + this.set('checked', newValue, priorityChange); + } + }, + _getValueAttr: function(){ + // summary: + // Hook so attr('value') works. + // description: + // If the CheckBox is checked, returns the value attribute. + // Otherwise returns false. + return (this.checked ? this.value : false); + }, + + // Override dijit.form.Button._setLabelAttr() since we don't even have a containerNode. + // Normally users won't try to set label, except when CheckBox or RadioButton is the child of a dojox.layout.TabContainer + _setLabelAttr: undefined, + + postMixInProperties: function(){ + if(this.value == ""){ + this.value = "on"; + } + + // Need to set initial checked state as part of template, so that form submit works. + // dojo.attr(node, "checked", bool) doesn't work on IEuntil node has been attached + // to <body>, see #8666 + this.checkedAttrSetting = this.checked ? "checked" : ""; + + this.inherited(arguments); + }, + + _fillContent: function(/*DomNode*/ source){ + // Override Button::_fillContent() since it doesn't make sense for CheckBox, + // since CheckBox doesn't even have a container + }, + + reset: function(){ + // Override ToggleButton.reset() + + this._hasBeenBlurred = false; + + this.set('checked', this.params.checked || false); + + // Handle unlikely event that the <input type=checkbox> value attribute has changed + this.value = this.params.value || "on"; + dojo.attr(this.focusNode, 'value', this.value); + }, + + _onFocus: function(){ + if(this.id){ + dojo.query("label[for='"+this.id+"']").addClass("dijitFocusedLabel"); + } + this.inherited(arguments); + }, + + _onBlur: function(){ + if(this.id){ + dojo.query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel"); + } + this.inherited(arguments); + }, + + _onClick: function(/*Event*/ e){ + // summary: + // Internal function to handle click actions - need to check + // readOnly, since button no longer does that check. + if(this.readOnly){ + return false; + } + return this.inherited(arguments); + } + } +); + +dojo.declare( + "dijit.form.RadioButton", + dijit.form.CheckBox, + { + // summary: + // Same as an HTML radio, but with fancy styling. + + type: "radio", + baseClass: "dijitRadio", + + _setCheckedAttr: function(/*Boolean*/ value){ + // If I am being checked then have to deselect currently checked radio button + this.inherited(arguments); + if(!this._created){ return; } + if(value){ + var _this = this; + // search for radio buttons with the same name that need to be unchecked + dojo.query("INPUT[type=radio]", this.focusNode.form || dojo.doc).forEach( // can't use name= since dojo.query doesn't support [] in the name + function(inputNode){ + if(inputNode.name == _this.name && inputNode != _this.focusNode && inputNode.form == _this.focusNode.form){ + var widget = dijit.getEnclosingWidget(inputNode); + if(widget && widget.checked){ + widget.set('checked', false); + } + } + } + ); + } + }, + + _clicked: function(/*Event*/ e){ + if(!this.checked){ + this.set('checked', true); + } + } + } +); + +} + +if(!dojo._hasResource["dijit.form.DropDownButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.DropDownButton"] = true; +dojo.provide("dijit.form.DropDownButton"); + + + +} + +if(!dojo._hasResource["dojo.regexp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.regexp"] = true; +dojo.provide("dojo.regexp"); + +/*===== +dojo.regexp = { + // summary: Regular expressions and Builder resources +}; +=====*/ + +dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){ + // summary: + // Adds escape sequences for special characters in regular expressions + // except: + // a String with special characters to be left unescaped + + return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){ + if(except && except.indexOf(ch) != -1){ + return ch; + } + return "\\" + ch; + }); // String +} + +dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){ + // summary: + // Builds a regular expression that groups subexpressions + // description: + // A utility function used by some of the RE generators. The + // subexpressions are constructed by the function, re, in the second + // parameter. re builds one subexpression for each elem in the array + // a, in the first parameter. Returns a string for a regular + // expression that groups all the subexpressions. + // arr: + // A single value or an array of values. + // re: + // A function. Takes one parameter and converts it to a regular + // expression. + // nonCapture: + // If true, uses non-capturing match, otherwise matches are retained + // by regular expression. Defaults to false + + // case 1: a is a single value. + if(!(arr instanceof Array)){ + return re(arr); // String + } + + // case 2: a is an array + var b = []; + for(var i = 0; i < arr.length; i++){ + // convert each elem to a RE + b.push(re(arr[i])); + } + + // join the REs as alternatives in a RE group. + return dojo.regexp.group(b.join("|"), nonCapture); // String +} + +dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){ + // summary: + // adds group match to expression + // nonCapture: + // If true, uses non-capturing match, otherwise matches are retained + // by regular expression. + return "(" + (nonCapture ? "?:":"") + expression + ")"; // String +} + +} + +if(!dojo._hasResource["dojo.data.util.sorter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.data.util.sorter"] = true; +dojo.provide("dojo.data.util.sorter"); + +dojo.data.util.sorter.basicComparator = function( /*anything*/ a, + /*anything*/ b){ + // summary: + // Basic comparision function that compares if an item is greater or less than another item + // description: + // returns 1 if a > b, -1 if a < b, 0 if equal. + // 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list. + // And compared to each other, null is equivalent to undefined. + + //null is a problematic compare, so if null, we set to undefined. + //Makes the check logic simple, compact, and consistent + //And (null == undefined) === true, so the check later against null + //works for undefined and is less bytes. + var r = -1; + if(a === null){ + a = undefined; + } + if(b === null){ + b = undefined; + } + if(a == b){ + r = 0; + }else if(a > b || a == null){ + r = 1; + } + return r; //int {-1,0,1} +}; + +dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortSpec, + /*dojo.data.core.Read*/ store){ + // summary: + // Helper function to generate the sorting function based off the list of sort attributes. + // description: + // The sort function creation will look for a property on the store called 'comparatorMap'. If it exists + // it will look in the mapping for comparisons function for the attributes. If one is found, it will + // use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates. + // Returns the sorting function for this particular list of attributes and sorting directions. + // + // sortSpec: array + // A JS object that array that defines out what attribute names to sort on and whether it should be descenting or asending. + // The objects should be formatted as follows: + // { + // attribute: "attributeName-string" || attribute, + // descending: true|false; // Default is false. + // } + // store: object + // The datastore object to look up item values from. + // + var sortFunctions=[]; + + function createSortFunction(attr, dir, comp, s){ + //Passing in comp and s (comparator and store), makes this + //function much faster. + return function(itemA, itemB){ + var a = s.getValue(itemA, attr); + var b = s.getValue(itemB, attr); + return dir * comp(a,b); //int + }; + } + var sortAttribute; + var map = store.comparatorMap; + var bc = dojo.data.util.sorter.basicComparator; + for(var i = 0; i < sortSpec.length; i++){ + sortAttribute = sortSpec[i]; + var attr = sortAttribute.attribute; + if(attr){ + var dir = (sortAttribute.descending) ? -1 : 1; + var comp = bc; + if(map){ + if(typeof attr !== "string" && ("toString" in attr)){ + attr = attr.toString(); + } + comp = map[attr] || bc; + } + sortFunctions.push(createSortFunction(attr, + dir, comp, store)); + } + } + return function(rowA, rowB){ + var i=0; + while(i < sortFunctions.length){ + var ret = sortFunctions[i++](rowA, rowB); + if(ret !== 0){ + return ret;//int + } + } + return 0; //int + }; // Function +}; + +} + +if(!dojo._hasResource["dojo.data.util.simpleFetch"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.data.util.simpleFetch"] = true; +dojo.provide("dojo.data.util.simpleFetch"); + + +dojo.data.util.simpleFetch.fetch = function(/* Object? */ request){ + // summary: + // The simpleFetch mixin is designed to serve as a set of function(s) that can + // be mixed into other datastore implementations to accelerate their development. + // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems() + // call by returning an array of all the found items that matched the query. The simpleFetch mixin + // is not designed to work for datastores that respond to a fetch() call by incrementally + // loading items, or sequentially loading partial batches of the result + // set. For datastores that mixin simpleFetch, simpleFetch + // implements a fetch method that automatically handles eight of the fetch() + // arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope + // The class mixing in simpleFetch should not implement fetch(), + // but should instead implement a _fetchItems() method. The _fetchItems() + // method takes three arguments, the keywordArgs object that was passed + // to fetch(), a callback function to be called when the result array is + // available, and an error callback to be called if something goes wrong. + // The _fetchItems() method should ignore any keywordArgs parameters for + // start, count, onBegin, onItem, onComplete, onError, sort, and scope. + // The _fetchItems() method needs to correctly handle any other keywordArgs + // parameters, including the query parameter and any optional parameters + // (such as includeChildren). The _fetchItems() method should create an array of + // result items and pass it to the fetchHandler along with the original request object + // -- or, the _fetchItems() method may, if it wants to, create an new request object + // with other specifics about the request that are specific to the datastore and pass + // that as the request object to the handler. + // + // For more information on this specific function, see dojo.data.api.Read.fetch() + request = request || {}; + if(!request.store){ + request.store = this; + } + var self = this; + + var _errorHandler = function(errorData, requestObject){ + if(requestObject.onError){ + var scope = requestObject.scope || dojo.global; + requestObject.onError.call(scope, errorData, requestObject); + } + }; + + var _fetchHandler = function(items, requestObject){ + var oldAbortFunction = requestObject.abort || null; + var aborted = false; + + var startIndex = requestObject.start?requestObject.start:0; + var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length; + + requestObject.abort = function(){ + aborted = true; + if(oldAbortFunction){ + oldAbortFunction.call(requestObject); + } + }; + + var scope = requestObject.scope || dojo.global; + if(!requestObject.store){ + requestObject.store = self; + } + if(requestObject.onBegin){ + requestObject.onBegin.call(scope, items.length, requestObject); + } + if(requestObject.sort){ + items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self)); + } + if(requestObject.onItem){ + for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){ + var item = items[i]; + if(!aborted){ + requestObject.onItem.call(scope, item, requestObject); + } + } + } + if(requestObject.onComplete && !aborted){ + var subset = null; + if(!requestObject.onItem){ + subset = items.slice(startIndex, endIndex); + } + requestObject.onComplete.call(scope, subset, requestObject); + } + }; + this._fetchItems(request, _fetchHandler, _errorHandler); + return request; // Object +}; + +} + +if(!dojo._hasResource["dojo.data.util.filter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.data.util.filter"] = true; +dojo.provide("dojo.data.util.filter"); + +dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){ + // summary: + // Helper function to convert a simple pattern to a regular expression for matching. + // description: + // Returns a regular expression object that conforms to the defined conversion rules. + // For example: + // ca* -> /^ca.*$/ + // *ca* -> /^.*ca.*$/ + // *c\*a* -> /^.*c\*a.*$/ + // *c\*a?* -> /^.*c\*a..*$/ + // and so on. + // + // pattern: string + // A simple matching pattern to convert that follows basic rules: + // * Means match anything, so ca* means match anything starting with ca + // ? Means match single character. So, b?b will match to bob and bab, and so on. + // \ is an escape character. So for example, \* means do not treat * as a match, but literal character *. + // To use a \ as a character in the string, it must be escaped. So in the pattern it should be + // represented by \\ to be treated as an ordinary \ character instead of an escape. + // + // ignoreCase: + // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing + // By default, it is assumed case sensitive. + + var rxp = "^"; + var c = null; + for(var i = 0; i < pattern.length; i++){ + c = pattern.charAt(i); + switch(c){ + case '\\': + rxp += c; + i++; + rxp += pattern.charAt(i); + break; + case '*': + rxp += ".*"; break; + case '?': + rxp += "."; break; + case '$': + case '^': + case '/': + case '+': + case '.': + case '|': + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + rxp += "\\"; //fallthrough + default: + rxp += c; + } + } + rxp += "$"; + if(ignoreCase){ + return new RegExp(rxp,"mi"); //RegExp + }else{ + return new RegExp(rxp,"m"); //RegExp + } + +}; + +} + +if(!dojo._hasResource["dijit.form.TextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.TextBox"] = true; +dojo.provide("dijit.form.TextBox"); + + + +dojo.declare( + "dijit.form.TextBox", + dijit.form._FormValueWidget, + { + // summary: + // A base class for textbox form inputs + + // trim: Boolean + // Removes leading and trailing whitespace if true. Default is false. + trim: false, + + // uppercase: Boolean + // Converts all characters to uppercase if true. Default is false. + uppercase: false, + + // lowercase: Boolean + // Converts all characters to lowercase if true. Default is false. + lowercase: false, + + // propercase: Boolean + // Converts the first character of each word to uppercase if true. + propercase: false, + + // maxLength: String + // HTML INPUT tag maxLength declaration. + maxLength: "", + + // selectOnClick: [const] Boolean + // If true, all text will be selected when focused with mouse + selectOnClick: false, + + // placeHolder: String + // Defines a hint to help users fill out the input field (as defined in HTML 5). + // This should only contain plain text (no html markup). + placeHolder: "", + + templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"), + _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" dojoAttachPoint="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />', + + _buttonInputDisabled: dojo.isIE ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events + + baseClass: "dijitTextBox", + + attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { + maxLength: "focusNode" + }), + + postMixInProperties: function(){ + var type = this.type.toLowerCase(); + if(this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == dijit.form.TextBox.prototype.templateString)){ + this.templateString = this._singleNodeTemplate; + } + this.inherited(arguments); + }, + + _setPlaceHolderAttr: function(v){ + this.placeHolder = v; + if(!this._phspan){ + this._attachPoints.push('_phspan'); + /* dijitInputField class gives placeHolder same padding as the input field + * parent node already has dijitInputField class but it doesn't affect this <span> + * since it's position: absolute. + */ + this._phspan = dojo.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after'); + } + this._phspan.innerHTML=""; + this._phspan.appendChild(document.createTextNode(v)); + + this._updatePlaceHolder(); + }, + + _updatePlaceHolder: function(){ + if(this._phspan){ + this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none"; + } + }, + + _getValueAttr: function(){ + // summary: + // Hook so attr('value') works as we like. + // description: + // For `dijit.form.TextBox` this basically returns the value of the <input>. + // + // For `dijit.form.MappedTextBox` subclasses, which have both + // a "displayed value" and a separate "submit value", + // This treats the "displayed value" as the master value, computing the + // submit value from it via this.parse(). + return this.parse(this.get('displayedValue'), this.constraints); + }, + + _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ + // summary: + // Hook so attr('value', ...) works. + // + // description: + // Sets the value of the widget to "value" which can be of + // any type as determined by the widget. + // + // value: + // The visual element value is also set to a corresponding, + // but not necessarily the same, value. + // + // formattedValue: + // If specified, used to set the visual element value, + // otherwise a computed visual value is used. + // + // priorityChange: + // If true, an onChange event is fired immediately instead of + // waiting for the next blur event. + + var filteredValue; + if(value !== undefined){ + // TODO: this is calling filter() on both the display value and the actual value. + // I added a comment to the filter() definition about this, but it should be changed. + filteredValue = this.filter(value); + if(typeof formattedValue != "string"){ + if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){ + formattedValue = this.filter(this.format(filteredValue, this.constraints)); + }else{ formattedValue = ''; } + } + } + if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){ + this.textbox.value = formattedValue; + } + + this._updatePlaceHolder(); + + this.inherited(arguments, [filteredValue, priorityChange]); + }, + + // displayedValue: String + // For subclasses like ComboBox where the displayed value + // (ex: Kentucky) and the serialized value (ex: KY) are different, + // this represents the displayed value. + // + // Setting 'displayedValue' through attr('displayedValue', ...) + // updates 'value', and vice-versa. Otherwise 'value' is updated + // from 'displayedValue' periodically, like onBlur etc. + // + // TODO: move declaration to MappedTextBox? + // Problem is that ComboBox references displayedValue, + // for benefit of FilteringSelect. + displayedValue: "", + + getDisplayedValue: function(){ + // summary: + // Deprecated. Use set('displayedValue') instead. + // tags: + // deprecated + dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0"); + return this.get('displayedValue'); + }, + + _getDisplayedValueAttr: function(){ + // summary: + // Hook so attr('displayedValue') works. + // description: + // Returns the displayed value (what the user sees on the screen), + // after filtering (ie, trimming spaces etc.). + // + // For some subclasses of TextBox (like ComboBox), the displayed value + // is different from the serialized value that's actually + // sent to the server (see dijit.form.ValidationTextBox.serialize) + + return this.filter(this.textbox.value); + }, + + setDisplayedValue: function(/*String*/value){ + // summary: + // Deprecated. Use set('displayedValue', ...) instead. + // tags: + // deprecated + dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0"); + this.set('displayedValue', value); + }, + + _setDisplayedValueAttr: function(/*String*/value){ + // summary: + // Hook so attr('displayedValue', ...) works. + // description: + // Sets the value of the visual element to the string "value". + // The widget value is also set to a corresponding, + // but not necessarily the same, value. + + if(value === null || value === undefined){ value = '' } + else if(typeof value != "string"){ value = String(value) } + this.textbox.value = value; + this._setValueAttr(this.get('value'), undefined, value); + }, + + format: function(/* String */ value, /* Object */ constraints){ + // summary: + // Replacable function to convert a value to a properly formatted string. + // tags: + // protected extension + return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value)); + }, + + parse: function(/* String */ value, /* Object */ constraints){ + // summary: + // Replacable function to convert a formatted string to a value + // tags: + // protected extension + + return value; // String + }, + + _refreshState: function(){ + // summary: + // After the user types some characters, etc., this method is + // called to check the field for validity etc. The base method + // in `dijit.form.TextBox` does nothing, but subclasses override. + // tags: + // protected + }, + + _onInput: function(e){ + if(e && e.type && /key/i.test(e.type) && e.keyCode){ + switch(e.keyCode){ + case dojo.keys.SHIFT: + case dojo.keys.ALT: + case dojo.keys.CTRL: + case dojo.keys.TAB: + return; + } + } + if(this.intermediateChanges){ + var _this = this; + // the setTimeout allows the key to post to the widget input box + setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0); + } + this._refreshState(); + }, + + postCreate: function(){ + // setting the value here is needed since value="" in the template causes "undefined" + // and setting in the DOM (instead of the JS object) helps with form reset actions + if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE + var s = dojo.getComputedStyle(this.domNode); + if(s){ + var ff = s.fontFamily; + if(ff){ + var inputs = this.domNode.getElementsByTagName("INPUT"); + if(inputs){ + for(var i=0; i < inputs.length; i++){ + inputs[i].style.fontFamily = ff; + } + } + } + } + } + this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values shuld be the same + this.inherited(arguments); + if(dojo.isMoz || dojo.isOpera){ + this.connect(this.textbox, "oninput", this._onInput); + }else{ + this.connect(this.textbox, "onkeydown", this._onInput); + this.connect(this.textbox, "onkeyup", this._onInput); + this.connect(this.textbox, "onpaste", this._onInput); + this.connect(this.textbox, "oncut", this._onInput); + } + }, + + _blankValue: '', // if the textbox is blank, what value should be reported + filter: function(val){ + // summary: + // Auto-corrections (such as trimming) that are applied to textbox + // value on blur or form submit. + // description: + // For MappedTextBox subclasses, this is called twice + // - once with the display value + // - once the value as set/returned by attr('value', ...) + // and attr('value'), ex: a Number for NumberTextBox. + // + // In the latter case it does corrections like converting null to NaN. In + // the former case the NumberTextBox.filter() method calls this.inherited() + // to execute standard trimming code in TextBox.filter(). + // + // TODO: break this into two methods in 2.0 + // + // tags: + // protected extension + if(val === null){ return this._blankValue; } + if(typeof val != "string"){ return val; } + if(this.trim){ + val = dojo.trim(val); + } + if(this.uppercase){ + val = val.toUpperCase(); + } + if(this.lowercase){ + val = val.toLowerCase(); + } + if(this.propercase){ + val = val.replace(/[^\s]+/g, function(word){ + return word.substring(0,1).toUpperCase() + word.substring(1); + }); + } + return val; + }, + + _setBlurValue: function(){ + this._setValueAttr(this.get('value'), true); + }, + + _onBlur: function(e){ + if(this.disabled){ return; } + this._setBlurValue(); + this.inherited(arguments); + + if(this._selectOnClickHandle){ + this.disconnect(this._selectOnClickHandle); + } + if(this.selectOnClick && dojo.isMoz){ + this.textbox.selectionStart = this.textbox.selectionEnd = undefined; // clear selection so that the next mouse click doesn't reselect + } + + this._updatePlaceHolder(); + }, + + _onFocus: function(/*String*/ by){ + if(this.disabled || this.readOnly){ return; } + + // Select all text on focus via click if nothing already selected. + // Since mouse-up will clear the selection need to defer selection until after mouse-up. + // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event. + if(this.selectOnClick && by == "mouse"){ + this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){ + // Only select all text on first click; otherwise users would have no way to clear + // the selection. + this.disconnect(this._selectOnClickHandle); + + // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up) + // and if not, then select all the text + var textIsNotSelected; + if(dojo.isIE){ + var range = dojo.doc.selection.createRange(); + var parent = range.parentElement(); + textIsNotSelected = parent == this.textbox && range.text.length == 0; + }else{ + textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd; + } + if(textIsNotSelected){ + dijit.selectInputText(this.textbox); + } + }); + } + + this._updatePlaceHolder(); + + this._refreshState(); + this.inherited(arguments); + }, + + reset: function(){ + // Overrides dijit._FormWidget.reset(). + // Additionally resets the displayed textbox value to '' + this.textbox.value = ''; + this.inherited(arguments); + } + } +); + +dijit.selectInputText = function(/*DomNode*/element, /*Number?*/ start, /*Number?*/ stop){ + // summary: + // Select text in the input element argument, from start (default 0), to stop (default end). + + // TODO: use functions in _editor/selection.js? + var _window = dojo.global; + var _document = dojo.doc; + element = dojo.byId(element); + if(isNaN(start)){ start = 0; } + if(isNaN(stop)){ stop = element.value ? element.value.length : 0; } + dijit.focus(element); + if(_document["selection"] && dojo.body()["createTextRange"]){ // IE + if(element.createTextRange){ + var range = element.createTextRange(); + with(range){ + collapse(true); + moveStart("character", -99999); // move to 0 + moveStart("character", start); // delta from 0 is the correct position + moveEnd("character", stop-start); + select(); + } + } + }else if(_window["getSelection"]){ + if(element.setSelectionRange){ + element.setSelectionRange(start, stop); + } + } +}; + +} + +if(!dojo._hasResource["dijit.Tooltip"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.Tooltip"] = true; +dojo.provide("dijit.Tooltip"); + + + + +dojo.declare( + "dijit._MasterTooltip", + [dijit._Widget, dijit._Templated], + { + // summary: + // Internal widget that holds the actual tooltip markup, + // which occurs once per page. + // Called by Tooltip widgets which are just containers to hold + // the markup + // tags: + // protected + + // duration: Integer + // Milliseconds to fade in/fade out + duration: dijit.defaultDuration, + + templateString: dojo.cache("dijit", "templates/Tooltip.html", "<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\">\n\t<div class=\"dijitTooltipContainer dijitTooltipContents\" dojoAttachPoint=\"containerNode\" waiRole='alert'></div>\n\t<div class=\"dijitTooltipConnector\"></div>\n</div>\n"), + + postCreate: function(){ + dojo.body().appendChild(this.domNode); + + this.bgIframe = new dijit.BackgroundIframe(this.domNode); + + // Setup fade-in and fade-out functions. + this.fadeIn = dojo.fadeIn({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onShow") }); + this.fadeOut = dojo.fadeOut({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onHide") }); + + }, + + show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){ + // summary: + // Display tooltip w/specified contents to right of specified node + // (To left if there's no space on the right, or if rtl == true) + + if(this.aroundNode && this.aroundNode === aroundNode){ + return; + } + + if(this.fadeOut.status() == "playing"){ + // previous tooltip is being hidden; wait until the hide completes then show new one + this._onDeck=arguments; + return; + } + this.containerNode.innerHTML=innerHTML; + + var pos = dijit.placeOnScreenAroundElement(this.domNode, aroundNode, dijit.getPopupAroundAlignment((position && position.length) ? position : dijit.Tooltip.defaultPosition, !rtl), dojo.hitch(this, "orient")); + + // show it + dojo.style(this.domNode, "opacity", 0); + this.fadeIn.play(); + this.isShowingNow = true; + this.aroundNode = aroundNode; + }, + + orient: function(/* DomNode */ node, /* String */ aroundCorner, /* String */ tooltipCorner){ + // summary: + // Private function to set CSS for tooltip node based on which position it's in. + // This is called by the dijit popup code. + // tags: + // protected + + node.className = "dijitTooltip " + + { + "BL-TL": "dijitTooltipBelow dijitTooltipABLeft", + "TL-BL": "dijitTooltipAbove dijitTooltipABLeft", + "BR-TR": "dijitTooltipBelow dijitTooltipABRight", + "TR-BR": "dijitTooltipAbove dijitTooltipABRight", + "BR-BL": "dijitTooltipRight", + "BL-BR": "dijitTooltipLeft" + }[aroundCorner + "-" + tooltipCorner]; + }, + + _onShow: function(){ + // summary: + // Called at end of fade-in operation + // tags: + // protected + if(dojo.isIE){ + // the arrow won't show up on a node w/an opacity filter + this.domNode.style.filter=""; + } + }, + + hide: function(aroundNode){ + // summary: + // Hide the tooltip + if(this._onDeck && this._onDeck[1] == aroundNode){ + // this hide request is for a show() that hasn't even started yet; + // just cancel the pending show() + this._onDeck=null; + }else if(this.aroundNode === aroundNode){ + // this hide request is for the currently displayed tooltip + this.fadeIn.stop(); + this.isShowingNow = false; + this.aroundNode = null; + this.fadeOut.play(); + }else{ + // just ignore the call, it's for a tooltip that has already been erased + } + }, + + _onHide: function(){ + // summary: + // Called at end of fade-out operation + // tags: + // protected + + this.domNode.style.cssText=""; // to position offscreen again + this.containerNode.innerHTML=""; + if(this._onDeck){ + // a show request has been queued up; do it now + this.show.apply(this, this._onDeck); + this._onDeck=null; + } + } + + } +); + +dijit.showTooltip = function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){ + // summary: + // Display tooltip w/specified contents in specified position. + // See description of dijit.Tooltip.defaultPosition for details on position parameter. + // If position is not specified then dijit.Tooltip.defaultPosition is used. + if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); } + return dijit._masterTT.show(innerHTML, aroundNode, position, rtl); +}; + +dijit.hideTooltip = function(aroundNode){ + // summary: + // Hide the tooltip + if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); } + return dijit._masterTT.hide(aroundNode); +}; + +dojo.declare( + "dijit.Tooltip", + dijit._Widget, + { + // summary: + // Pops up a tooltip (a help message) when you hover over a node. + + // label: String + // Text to display in the tooltip. + // Specified as innerHTML when creating the widget from markup. + label: "", + + // showDelay: Integer + // Number of milliseconds to wait after hovering over/focusing on the object, before + // the tooltip is displayed. + showDelay: 400, + + // connectId: [const] String[] + // Id's of domNodes to attach the tooltip to. + // When user hovers over any of the specified dom nodes, the tooltip will appear. + // + // Note: Currently connectId can only be specified on initialization, it cannot + // be changed via attr('connectId', ...) + // + // Note: in 2.0 this will be renamed to connectIds for less confusion. + connectId: [], + + // position: String[] + // See description of `dijit.Tooltip.defaultPosition` for details on position parameter. + position: [], + + constructor: function(){ + // Map id's of nodes I'm connected to to a list of the this.connect() handles + this._nodeConnectionsById = {}; + }, + + _setConnectIdAttr: function(newIds){ + for(var oldId in this._nodeConnectionsById){ + this.removeTarget(oldId); + } + dojo.forEach(dojo.isArrayLike(newIds) ? newIds : [newIds], this.addTarget, this); + }, + + _getConnectIdAttr: function(){ + var ary = []; + for(var id in this._nodeConnectionsById){ + ary.push(id); + } + return ary; + }, + + addTarget: function(/*DOMNODE || String*/ id){ + // summary: + // Attach tooltip to specified node, if it's not already connected + var node = dojo.byId(id); + if(!node){ return; } + if(node.id in this._nodeConnectionsById){ return; }//Already connected + + this._nodeConnectionsById[node.id] = [ + this.connect(node, "onmouseenter", "_onTargetMouseEnter"), + this.connect(node, "onmouseleave", "_onTargetMouseLeave"), + this.connect(node, "onfocus", "_onTargetFocus"), + this.connect(node, "onblur", "_onTargetBlur") + ]; + }, + + removeTarget: function(/*DOMNODE || String*/ node){ + // summary: + // Detach tooltip from specified node + + // map from DOMNode back to plain id string + var id = node.id || node; + + if(id in this._nodeConnectionsById){ + dojo.forEach(this._nodeConnectionsById[id], this.disconnect, this); + delete this._nodeConnectionsById[id]; + } + }, + + postCreate: function(){ + dojo.addClass(this.domNode,"dijitTooltipData"); + }, + + startup: function(){ + this.inherited(arguments); + + // If this tooltip was created in a template, or for some other reason the specified connectId[s] + // didn't exist during the widget's initialization, then connect now. + var ids = this.connectId; + dojo.forEach(dojo.isArrayLike(ids) ? ids : [ids], this.addTarget, this); + }, + + _onTargetMouseEnter: function(/*Event*/ e){ + // summary: + // Handler for mouseenter event on the target node + // tags: + // private + this._onHover(e); + }, + + _onTargetMouseLeave: function(/*Event*/ e){ + // summary: + // Handler for mouseleave event on the target node + // tags: + // private + this._onUnHover(e); + }, + + _onTargetFocus: function(/*Event*/ e){ + // summary: + // Handler for focus event on the target node + // tags: + // private + + this._focus = true; + this._onHover(e); + }, + + _onTargetBlur: function(/*Event*/ e){ + // summary: + // Handler for blur event on the target node + // tags: + // private + + this._focus = false; + this._onUnHover(e); + }, + + _onHover: function(/*Event*/ e){ + // summary: + // Despite the name of this method, it actually handles both hover and focus + // events on the target node, setting a timer to show the tooltip. + // tags: + // private + if(!this._showTimer){ + var target = e.target; + this._showTimer = setTimeout(dojo.hitch(this, function(){this.open(target)}), this.showDelay); + } + }, + + _onUnHover: function(/*Event*/ e){ + // summary: + // Despite the name of this method, it actually handles both mouseleave and blur + // events on the target node, hiding the tooltip. + // tags: + // private + + // keep a tooltip open if the associated element still has focus (even though the + // mouse moved away) + if(this._focus){ return; } + + if(this._showTimer){ + clearTimeout(this._showTimer); + delete this._showTimer; + } + this.close(); + }, + + open: function(/*DomNode*/ target){ + // summary: + // Display the tooltip; usually not called directly. + // tags: + // private + + if(this._showTimer){ + clearTimeout(this._showTimer); + delete this._showTimer; + } + dijit.showTooltip(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight()); + + this._connectNode = target; + this.onShow(target, this.position); + }, + + close: function(){ + // summary: + // Hide the tooltip or cancel timer for show of tooltip + // tags: + // private + + if(this._connectNode){ + // if tooltip is currently shown + dijit.hideTooltip(this._connectNode); + delete this._connectNode; + this.onHide(); + } + if(this._showTimer){ + // if tooltip is scheduled to be shown (after a brief delay) + clearTimeout(this._showTimer); + delete this._showTimer; + } + }, + + onShow: function(target, position){ + // summary: + // Called when the tooltip is shown + // tags: + // callback + }, + + onHide: function(){ + // summary: + // Called when the tooltip is hidden + // tags: + // callback + }, + + uninitialize: function(){ + this.close(); + this.inherited(arguments); + } + } +); + +// dijit.Tooltip.defaultPosition: String[] +// This variable controls the position of tooltips, if the position is not specified to +// the Tooltip widget or *TextBox widget itself. It's an array of strings with the following values: +// +// * before: places tooltip to the left of the target node/widget, or to the right in +// the case of RTL scripts like Hebrew and Arabic +// * after: places tooltip to the right of the target node/widget, or to the left in +// the case of RTL scripts like Hebrew and Arabic +// * above: tooltip goes above target node +// * below: tooltip goes below target node +// +// The list is positions is tried, in order, until a position is found where the tooltip fits +// within the viewport. +// +// Be careful setting this parameter. A value of "above" may work fine until the user scrolls +// the screen so that there's no room above the target node. Nodes with drop downs, like +// DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure +// that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there +// is only room below (or above) the target node, but not both. +dijit.Tooltip.defaultPosition = ["after", "before"]; + +} + +if(!dojo._hasResource["dijit.form.ValidationTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.ValidationTextBox"] = true; +dojo.provide("dijit.form.ValidationTextBox"); + + + + + + + + +/*===== + dijit.form.ValidationTextBox.__Constraints = function(){ + // locale: String + // locale used for validation, picks up value from this widget's lang attribute + // _flags_: anything + // various flags passed to regExpGen function + this.locale = ""; + this._flags_ = ""; + } +=====*/ + +dojo.declare( + "dijit.form.ValidationTextBox", + dijit.form.TextBox, + { + // summary: + // Base class for textbox widgets with the ability to validate content of various types and provide user feedback. + // tags: + // protected + + templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"), + baseClass: "dijitTextBox dijitValidationTextBox", + + // required: Boolean + // User is required to enter data into this field. + required: false, + + // promptMessage: String + // If defined, display this hint string immediately on focus to the textbox, if empty. + // Think of this like a tooltip that tells the user what to do, not an error message + // that tells the user what they've done wrong. + // + // Message disappears when user starts typing. + promptMessage: "", + + // invalidMessage: String + // The message to display if value is invalid. + // The translated string value is read from the message file by default. + // Set to "" to use the promptMessage instead. + invalidMessage: "$_unset_$", + + // missingMessage: String + // The message to display if value is empty and the field is required. + // The translated string value is read from the message file by default. + // Set to "" to use the invalidMessage instead. + missingMessage: "$_unset_$", + + // constraints: dijit.form.ValidationTextBox.__Constraints + // user-defined object needed to pass parameters to the validator functions + constraints: {}, + + // regExp: [extension protected] String + // regular expression string used to validate the input + // Do not specify both regExp and regExpGen + regExp: ".*", + + regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/constraints){ + // summary: + // Overridable function used to generate regExp when dependent on constraints. + // Do not specify both regExp and regExpGen. + // tags: + // extension protected + return this.regExp; // String + }, + + // state: [readonly] String + // Shows current state (ie, validation result) of input (Normal, Warning, or Error) + state: "", + + // tooltipPosition: String[] + // See description of `dijit.Tooltip.defaultPosition` for details on this parameter. + tooltipPosition: [], + + _setValueAttr: function(){ + // summary: + // Hook so attr('value', ...) works. + this.inherited(arguments); + this.validate(this._focused); + }, + + validator: function(/*anything*/value, /*dijit.form.ValidationTextBox.__Constraints*/constraints){ + // summary: + // Overridable function used to validate the text input against the regular expression. + // tags: + // protected + return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) && + (!this.required || !this._isEmpty(value)) && + (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean + }, + + _isValidSubset: function(){ + // summary: + // Returns true if the value is either already valid or could be made valid by appending characters. + // This is used for validation while the user [may be] still typing. + return this.textbox.value.search(this._partialre) == 0; + }, + + isValid: function(/*Boolean*/ isFocused){ + // summary: + // Tests if value is valid. + // Can override with your own routine in a subclass. + // tags: + // protected + return this.validator(this.textbox.value, this.constraints); + }, + + _isEmpty: function(value){ + // summary: + // Checks for whitespace + return /^\s*$/.test(value); // Boolean + }, + + getErrorMessage: function(/*Boolean*/ isFocused){ + // summary: + // Return an error message to show if appropriate + // tags: + // protected + return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String + }, + + getPromptMessage: function(/*Boolean*/ isFocused){ + // summary: + // Return a hint message to show when widget is first focused + // tags: + // protected + return this.promptMessage; // String + }, + + _maskValidSubsetError: true, + validate: function(/*Boolean*/ isFocused){ + // summary: + // Called by oninit, onblur, and onkeypress. + // description: + // Show missing or invalid messages if appropriate, and highlight textbox field. + // tags: + // protected + var message = ""; + var isValid = this.disabled || this.isValid(isFocused); + if(isValid){ this._maskValidSubsetError = true; } + var isEmpty = this._isEmpty(this.textbox.value); + var isValidSubset = !isValid && !isEmpty && isFocused && this._isValidSubset(); + this.state = ((isValid || ((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "" : "Error"; + if(this.state == "Error"){ this._maskValidSubsetError = isFocused; } // we want the error to show up afer a blur and refocus + this._setStateClass(); + dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true"); + if(isFocused){ + if(this.state == "Error"){ + message = this.getErrorMessage(true); + }else{ + message = this.getPromptMessage(true); // show the prompt whever there's no error + } + this._maskValidSubsetError = true; // since we're focused, always mask warnings + } + this.displayMessage(message); + return isValid; + }, + + // _message: String + // Currently displayed message + _message: "", + + displayMessage: function(/*String*/ message){ + // summary: + // Overridable method to display validation errors/hints. + // By default uses a tooltip. + // tags: + // extension + if(this._message == message){ return; } + this._message = message; + dijit.hideTooltip(this.domNode); + if(message){ + dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); + } + }, + + _refreshState: function(){ + // Overrides TextBox._refreshState() + this.validate(this._focused); + this.inherited(arguments); + }, + + //////////// INITIALIZATION METHODS /////////////////////////////////////// + + constructor: function(){ + this.constraints = {}; + }, + + _setConstraintsAttr: function(/* Object */ constraints){ + if(!constraints.locale && this.lang){ + constraints.locale = this.lang; + } + this.constraints = constraints; + this._computePartialRE(); + }, + + _computePartialRE: function(){ + var p = this.regExpGen(this.constraints); + this.regExp = p; + var partialre = ""; + // parse the regexp and produce a new regexp that matches valid subsets + // if the regexp is .* then there's no use in matching subsets since everything is valid + if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g, + function (re){ + switch(re.charAt(0)){ + case '{': + case '+': + case '?': + case '*': + case '^': + case '$': + case '|': + case '(': + partialre += re; + break; + case ")": + partialre += "|$)"; + break; + default: + partialre += "(?:"+re+"|$)"; + break; + } + } + );} + try{ // this is needed for now since the above regexp parsing needs more test verification + "".search(partialre); + }catch(e){ // should never be here unless the original RE is bad or the parsing is bad + partialre = this.regExp; + console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp); + } // should never be here unless the original RE is bad or the parsing is bad + this._partialre = "^(?:" + partialre + ")$"; + }, + + postMixInProperties: function(){ + this.inherited(arguments); + this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang); + if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this.messages.invalidMessage; } + if(!this.invalidMessage){ this.invalidMessage = this.promptMessage; } + if(this.missingMessage == "$_unset_$"){ this.missingMessage = this.messages.missingMessage; } + if(!this.missingMessage){ this.missingMessage = this.invalidMessage; } + this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + this.inherited(arguments); // call FormValueWidget._setDisabledAttr() + this._refreshState(); + }, + + _setRequiredAttr: function(/*Boolean*/ value){ + this.required = value; + dijit.setWaiState(this.focusNode, "required", value); + this._refreshState(); + }, + + reset:function(){ + // Overrides dijit.form.TextBox.reset() by also + // hiding errors about partial matches + this._maskValidSubsetError = true; + this.inherited(arguments); + }, + + _onBlur: function(){ + this.displayMessage(''); + this.inherited(arguments); + } + } +); + +dojo.declare( + "dijit.form.MappedTextBox", + dijit.form.ValidationTextBox, + { + // summary: + // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have + // a visible formatted display value, and a serializable + // value in a hidden input field which is actually sent to the server. + // description: + // The visible display may + // be locale-dependent and interactive. The value sent to the server is stored in a hidden + // input field which uses the `name` attribute declared by the original widget. That value sent + // to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically + // locale-neutral. + // tags: + // protected + + postMixInProperties: function(){ + this.inherited(arguments); + + // we want the name attribute to go to the hidden <input>, not the displayed <input>, + // so override _FormWidget.postMixInProperties() setting of nameAttrSetting + this.nameAttrSetting = ""; + }, + + serialize: function(/*anything*/val, /*Object?*/options){ + // summary: + // Overridable function used to convert the attr('value') result to a canonical + // (non-localized) string. For example, will print dates in ISO format, and + // numbers the same way as they are represented in javascript. + // tags: + // protected extension + return val.toString ? val.toString() : ""; // String + }, + + toString: function(){ + // summary: + // Returns widget as a printable string using the widget's value + // tags: + // protected + var val = this.filter(this.get('value')); // call filter in case value is nonstring and filter has been customized + return val != null ? (typeof val == "string" ? val : this.serialize(val, this.constraints)) : ""; // String + }, + + validate: function(){ + // Overrides `dijit.form.TextBox.validate` + this.valueNode.value = this.toString(); + return this.inherited(arguments); + }, + + buildRendering: function(){ + // Overrides `dijit._Templated.buildRendering` + + this.inherited(arguments); + + // Create a hidden <input> node with the serialized value used for submit + // (as opposed to the displayed value). + // Passing in name as markup rather than calling dojo.create() with an attrs argument + // to make dojo.query(input[name=...]) work on IE. (see #8660) + this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name + "'" : "") + ">", this.textbox, "after"); + }, + + reset:function(){ + // Overrides `dijit.form.ValidationTextBox.reset` to + // reset the hidden textbox value to '' + this.valueNode.value = ''; + this.inherited(arguments); + } + } +); + +/*===== + dijit.form.RangeBoundTextBox.__Constraints = function(){ + // min: Number + // Minimum signed value. Default is -Infinity + // max: Number + // Maximum signed value. Default is +Infinity + this.min = min; + this.max = max; + } +=====*/ + +dojo.declare( + "dijit.form.RangeBoundTextBox", + dijit.form.MappedTextBox, + { + // summary: + // Base class for textbox form widgets which defines a range of valid values. + + // rangeMessage: String + // The message to display if value is out-of-range + rangeMessage: "", + + /*===== + // constraints: dijit.form.RangeBoundTextBox.__Constraints + constraints: {}, + ======*/ + + rangeCheck: function(/*Number*/ primitive, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints){ + // summary: + // Overridable function used to validate the range of the numeric input value. + // tags: + // protected + return ("min" in constraints? (this.compare(primitive,constraints.min) >= 0) : true) && + ("max" in constraints? (this.compare(primitive,constraints.max) <= 0) : true); // Boolean + }, + + isInRange: function(/*Boolean*/ isFocused){ + // summary: + // Tests if the value is in the min/max range specified in constraints + // tags: + // protected + return this.rangeCheck(this.get('value'), this.constraints); + }, + + _isDefinitelyOutOfRange: function(){ + // summary: + // Returns true if the value is out of range and will remain + // out of range even if the user types more characters + var val = this.get('value'); + var isTooLittle = false; + var isTooMuch = false; + if("min" in this.constraints){ + var min = this.constraints.min; + min = this.compare(val, ((typeof min == "number") && min >= 0 && val !=0) ? 0 : min); + isTooLittle = (typeof min == "number") && min < 0; + } + if("max" in this.constraints){ + var max = this.constraints.max; + max = this.compare(val, ((typeof max != "number") || max > 0) ? max : 0); + isTooMuch = (typeof max == "number") && max > 0; + } + return isTooLittle || isTooMuch; + }, + + _isValidSubset: function(){ + // summary: + // Overrides `dijit.form.ValidationTextBox._isValidSubset`. + // Returns true if the input is syntactically valid, and either within + // range or could be made in range by more typing. + return this.inherited(arguments) && !this._isDefinitelyOutOfRange(); + }, + + isValid: function(/*Boolean*/ isFocused){ + // Overrides dijit.form.ValidationTextBox.isValid to check that the value is also in range. + return this.inherited(arguments) && + ((this._isEmpty(this.textbox.value) && !this.required) || this.isInRange(isFocused)); // Boolean + }, + + getErrorMessage: function(/*Boolean*/ isFocused){ + // Overrides dijit.form.ValidationTextBox.getErrorMessage to print "out of range" message if appropriate + var v = this.get('value'); + if(v !== null && v !== '' && v !== undefined && (typeof v != "number" || !isNaN(v)) && !this.isInRange(isFocused)){ // don't check isInRange w/o a real value + return this.rangeMessage; // String + } + return this.inherited(arguments); + }, + + postMixInProperties: function(){ + this.inherited(arguments); + if(!this.rangeMessage){ + this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang); + this.rangeMessage = this.messages.rangeMessage; + } + }, + + _setConstraintsAttr: function(/* Object */ constraints){ + this.inherited(arguments); + if(this.focusNode){ // not set when called from postMixInProperties + if(this.constraints.min !== undefined){ + dijit.setWaiState(this.focusNode, "valuemin", this.constraints.min); + }else{ + dijit.removeWaiState(this.focusNode, "valuemin"); + } + if(this.constraints.max !== undefined){ + dijit.setWaiState(this.focusNode, "valuemax", this.constraints.max); + }else{ + dijit.removeWaiState(this.focusNode, "valuemax"); + } + } + }, + + _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){ + // summary: + // Hook so attr('value', ...) works. + + dijit.setWaiState(this.focusNode, "valuenow", value); + this.inherited(arguments); + } + } +); + +} + +if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.ComboBox"] = true; +dojo.provide("dijit.form.ComboBox"); + + + + + + + + + + + + +dojo.declare( + "dijit.form.ComboBoxMixin", + null, + { + // summary: + // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect` + // description: + // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`. + // tags: + // protected + + // item: Object + // This is the item returned by the dojo.data.store implementation that + // provides the data for this ComboBox, it's the currently selected item. + item: null, + + // pageSize: Integer + // Argument to data provider. + // Specifies number of search results per page (before hitting "next" button) + pageSize: Infinity, + + // store: Object + // Reference to data provider object used by this ComboBox + store: null, + + // fetchProperties: Object + // Mixin to the dojo.data store's fetch. + // For example, to set the sort order of the ComboBox menu, pass: + // | { sort: {attribute:"name",descending: true} } + // To override the default queryOptions so that deep=false, do: + // | { queryOptions: {ignoreCase: true, deep: false} } + fetchProperties:{}, + + // query: Object + // A query that can be passed to 'store' to initially filter the items, + // before doing further filtering based on `searchAttr` and the key. + // Any reference to the `searchAttr` is ignored. + query: {}, + + // autoComplete: Boolean + // If user types 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, + + // highlightMatch: String + // One of: "first", "all" or "none". + // + // If the ComboBox/FilteringSelect opens with the search results and the searched + // string can be found, it will be highlighted. If set to "all" + // then will probably want to change `queryExpr` parameter to '*${0}*' + // + // Highlighting is only performed when `labelType` is "text", so as to not + // interfere with any HTML markup an HTML label might contain. + highlightMatch: "first", + + // searchDelay: Integer + // Delay in milliseconds between when user types something and we start + // searching based on that value + searchDelay: 100, + + // searchAttr: String + // Search for items in the data store where this attribute (in the item) + // matches what the user typed + searchAttr: "name", + + // labelAttr: String? + // The entries in the drop down list come from this attribute in the + // dojo.data items. + // If not specified, the searchAttr attribute is used instead. + labelAttr: "", + + // labelType: String + // Specifies how to interpret the labelAttr in the data store items. + // Can be "html" or "text". + labelType: "text", + + // queryExpr: String + // This specifies what query ComboBox/FilteringSelect sends to the data store, + // based on what the user has typed. Changing this expression will modify + // whether the drop down shows only exact matches, a "starting with" match, + // etc. Use it in conjunction with highlightMatch. + // dojo.data query expression pattern. + // `${0}` will be substituted for the user text. + // `*` is used for wildcards. + // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is" + queryExpr: "${0}*", + + // ignoreCase: Boolean + // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items + ignoreCase: true, + + // hasDownArrow: [const] Boolean + // Set this textbox to have a down arrow button, to display the drop down list. + // Defaults to true. + hasDownArrow: true, + + templateString: dojo.cache("dijit.form", "templates/ComboBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachPoint=\"comboNode\" waiRole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"downArrowNode\" waiRole=\"presentation\"\n\t\tdojoAttachEvent=\"onmousedown:_onArrowMouseDown\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onKeyPress,compositionend\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" waiRole=\"textbox\" waiState=\"haspopup-true,autocomplete-list\"\n\t/></div\n></div>\n"), + + baseClass: "dijitTextBox dijitComboBox", + + // Set classes like dijitDownArrowButtonHover depending on + // mouse action over button node + cssStateNodes: { + "downArrowNode": "dijitDownArrowButton" + }, + + _getCaretPos: function(/*DomNode*/ element){ + // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 + var pos = 0; + if(typeof(element.selectionStart) == "number"){ + // FIXME: this is totally borked on Moz < 1.3. Any recourse? + pos = element.selectionStart; + }else if(dojo.isIE){ + // in the case of a mouse click in a popup being handled, + // then the dojo.doc.selection is not the textarea, but the popup + // var r = dojo.doc.selection.createRange(); + // hack to get IE 6 to play nice. What a POS browser. + var tr = dojo.doc.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); + pos = String(ntr.text).replace(/\r/g,"").length; + }catch(e){ + // If focus has shifted, 0 is fine for caret pos. + } + } + return pos; + }, + + _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ + location = parseInt(location); + dijit.selectInputText(element, location, location); + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + // Additional code to set disabled state of ComboBox node. + // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr(). + this.inherited(arguments); + dijit.setWaiState(this.comboNode, "disabled", value); + }, + + _abortQuery: function(){ + // stop in-progress query + if(this.searchTimer){ + clearTimeout(this.searchTimer); + this.searchTimer = null; + } + if(this._fetchHandle){ + if(this._fetchHandle.abort){ this._fetchHandle.abort(); } + this._fetchHandle = null; + } + }, + + _onInput: function(/*Event*/ evt){ + // summary: + // Handles paste events + if(!this.searchTimer && (evt.type == 'paste'/*IE|WebKit*/ || evt.type == 'input'/*Firefox*/) && this._lastInput != this.textbox.value){ + this.searchTimer = setTimeout(dojo.hitch(this, function(){ + this._onKeyPress({charOrCode: 229}); // fake IME key to cause a search + }), 100); // long delay that will probably be preempted by keyboard input + } + this.inherited(arguments); + }, + + _onKeyPress: function(/*Event*/ evt){ + // summary: + // Handles keyboard events + var key = evt.charOrCode; + // except for cutting/pasting case - ctrl + x/v + if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){ + return; // throw out weird key combinations and spurious events + } + var doSearch = false; + var searchFunction = "_startSearchFromInput"; + var pw = this._popupWidget; + var dk = dojo.keys; + var highlighted = null; + this._prev_key_backspace = false; + this._abortQuery(); + if(this._isShowingNow){ + pw.handleKey(key); + highlighted = pw.getHighlightedOption(); + } + switch(key){ + case dk.PAGE_DOWN: + case dk.DOWN_ARROW: + case dk.PAGE_UP: + case dk.UP_ARROW: + if(!this._isShowingNow){ + doSearch = true; + searchFunction = "_startSearchAll"; + }else{ + this._announceOption(highlighted); + } + dojo.stopEvent(evt); + break; + + case dk.ENTER: + // prevent submitting form if user presses enter. Also + // prevent accepting the value if either Next or Previous + // are selected + if(highlighted){ + // only stop event on prev/next + if(highlighted == pw.nextButton){ + this._nextSearch(1); + dojo.stopEvent(evt); + break; + }else if(highlighted == pw.previousButton){ + this._nextSearch(-1); + dojo.stopEvent(evt); + break; + } + }else{ + // Update 'value' (ex: KY) according to currently displayed text + this._setBlurValue(); // set value if needed + this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting + } + // default case: + // prevent submit, but allow event to bubble + evt.preventDefault(); + // fall through + + case dk.TAB: + var newvalue = this.get('displayedValue'); + // if the user had More Choices selected fall into the + // _onBlur handler + if(pw && ( + newvalue == pw._messages["previousMessage"] || + newvalue == pw._messages["nextMessage"]) + ){ + break; + } + if(highlighted){ + this._selectOption(); + } + if(this._isShowingNow){ + this._lastQuery = null; // in case results come back later + this._hideResultList(); + } + break; + + case ' ': + if(highlighted){ + dojo.stopEvent(evt); + this._selectOption(); + this._hideResultList(); + }else{ + doSearch = true; + } + break; + + case dk.ESCAPE: + if(this._isShowingNow){ + dojo.stopEvent(evt); + this._hideResultList(); + } + break; + + case dk.DELETE: + case dk.BACKSPACE: + this._prev_key_backspace = true; + doSearch = true; + break; + + default: + // Non char keys (F1-F12 etc..) shouldn't open list. + // Ascii characters and IME input (Chinese, Japanese etc.) should. + // On IE and safari, IME input produces keycode == 229, and we simulate + // it on firefox by attaching to compositionend event (see compositionend method) + doSearch = typeof key == 'string' || key == 229; + } + if(doSearch){ + // need to wait a tad before start search so that the event + // bubbles through DOM and we have value visible + this.item = undefined; // undefined means item needs to be set + this.searchTimer = setTimeout(dojo.hitch(this, searchFunction),1); + } + }, + + _autoCompleteText: function(/*String*/ text){ + // summary: + // Fill in the textbox with the first item from the drop down + // list, and highlight the characters that were + // auto-completed. For example, if user typed "CA" and the + // drop down list appeared, the textbox would be changed to + // "California" and "ifornia" would be highlighted. + + var fn = this.focusNode; + + // IE7: clear selection so next highlight works all the time + dijit.selectInputText(fn, fn.value.length); + // does text autoComplete the value in the textbox? + var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr'; + if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){ + var cpos = this._getCaretPos(fn); + // only try to extend if we added the last character at the end of the input + if((cpos+1) > fn.value.length){ + // only add to input node as we would overwrite Capitalisation of chars + // actually, that is ok + fn.value = text;//.substr(cpos); + // visually highlight the autocompleted characters + dijit.selectInputText(fn, cpos); + } + }else{ + // text does not autoComplete; replace the whole value and highlight + fn.value = text; + dijit.selectInputText(fn); + } + }, + + _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ + this._fetchHandle = null; + if( this.disabled || + this.readOnly || + (dataObject.query[this.searchAttr] != this._lastQuery) + ){ + return; + } + this._popupWidget.clearResultList(); + if(!results.length && !this._maxOptions){ // this condition needs to match !this._isvalid set in FilteringSelect::_openResultList + this._hideResultList(); + return; + } + + + // Fill in the textbox with the first item from the drop down list, + // and highlight the characters that were auto-completed. For + // example, if user typed "CA" and the drop down list appeared, the + // textbox would be changed to "California" and "ifornia" would be + // highlighted. + + dataObject._maxOptions = this._maxOptions; + var nodes = this._popupWidget.createOptions( + results, + dataObject, + dojo.hitch(this, "_getMenuLabelFromItem") + ); + + // show our list (only if we have content, else nothing) + this._showResultList(); + + // #4091: + // tell the screen reader that the paging callback finished by + // shouting the next choice + if(dataObject.direction){ + if(1 == dataObject.direction){ + this._popupWidget.highlightFirstOption(); + }else if(-1 == dataObject.direction){ + this._popupWidget.highlightLastOption(); + } + this._announceOption(this._popupWidget.getHighlightedOption()); + }else if(this.autoComplete && !this._prev_key_backspace /*&& !dataObject.direction*/ + // when the user clicks the arrow button to show the full list, + // startSearch looks for "*". + // it does not make sense to autocomplete + // if they are just previewing the options available. + && !/^[*]+$/.test(dataObject.query[this.searchAttr])){ + this._announceOption(nodes[1]); // 1st real item + } + }, + + _showResultList: function(){ + this._hideResultList(); + // hide the tooltip + this.displayMessage(""); + + // Position the list and if it's too big to fit on the screen then + // size it to the maximum possible height + // Our dear friend IE doesnt take max-height so we need to + // calculate that on our own every time + + // TODO: want to redo this, see + // http://trac.dojotoolkit.org/ticket/3272 + // and + // http://trac.dojotoolkit.org/ticket/4108 + + + // natural size of the list has changed, so erase old + // width/height settings, which were hardcoded in a previous + // call to this function (via dojo.marginBox() call) + dojo.style(this._popupWidget.domNode, {width: "", height: ""}); + + var best = this.open(); + // #3212: + // only set auto scroll bars if necessary prevents issues with + // scroll bars appearing when they shouldn't when node is made + // wider (fractional pixels cause this) + var popupbox = dojo.marginBox(this._popupWidget.domNode); + this._popupWidget.domNode.style.overflow = + ((best.h == popupbox.h) && (best.w == popupbox.w)) ? "hidden" : "auto"; + // #4134: + // borrow TextArea scrollbar test so content isn't covered by + // scrollbar and horizontal scrollbar doesn't appear + var newwidth = best.w; + if(best.h < this._popupWidget.domNode.scrollHeight){ + newwidth += 16; + } + dojo.marginBox(this._popupWidget.domNode, { + h: best.h, + w: Math.max(newwidth, this.domNode.offsetWidth) + }); + + // If we increased the width of drop down to match the width of ComboBox.domNode, + // then need to reposition the drop down (wrapper) so (all of) the drop down still + // appears underneath the ComboBox.domNode + if(newwidth < this.domNode.offsetWidth){ + this._popupWidget.domNode.parentNode.style.left = dojo.position(this.domNode, true).x + "px"; + } + + dijit.setWaiState(this.comboNode, "expanded", "true"); + }, + + _hideResultList: function(){ + this._abortQuery(); + if(this._isShowingNow){ + dijit.popup.close(this._popupWidget); + this._isShowingNow=false; + dijit.setWaiState(this.comboNode, "expanded", "false"); + dijit.removeWaiState(this.focusNode,"activedescendant"); + } + }, + + _setBlurValue: function(){ + // if the user clicks away from the textbox OR tabs away, set the + // value to the textbox value + // #4617: + // if value is now more choices or previous choices, revert + // the value + var newvalue = this.get('displayedValue'); + var pw = this._popupWidget; + if(pw && ( + newvalue == pw._messages["previousMessage"] || + newvalue == pw._messages["nextMessage"] + ) + ){ + this._setValueAttr(this._lastValueReported, true); + }else if(typeof this.item == "undefined"){ + // Update 'value' (ex: KY) according to currently displayed text + this.item = null; + this.set('displayedValue', newvalue); + }else{ + if(this.value != this._lastValueReported){ + dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); + } + this._refreshState(); + } + }, + + _onBlur: function(){ + // summary: + // Called magically when focus has shifted away from this widget and it's drop down + this._hideResultList(); + this.inherited(arguments); + }, + + _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Set the displayed valued in the input box, and the hidden value + // that gets submitted, based on a dojo.data store item. + // description: + // Users shouldn't call this function; they should be calling + // attr('item', value) + // tags: + // private + if(!displayedValue){ displayedValue = this.labelFunc(item, this.store); } + this.value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue; + this.item = item; + dijit.form.ComboBox.superclass._setValueAttr.call(this, this.value, priorityChange, displayedValue); + }, + + _announceOption: function(/*Node*/ node){ + // summary: + // a11y code that puts the highlighted option in the textbox. + // This way screen readers will know what is happening in the + // menu. + + if(!node){ + return; + } + // pull the text value from the item attached to the DOM node + var newValue; + if(node == this._popupWidget.nextButton || + node == this._popupWidget.previousButton){ + newValue = node.innerHTML; + this.item = undefined; + this.value = ''; + }else{ + newValue = this.labelFunc(node.item, this.store); + this.set('item', node.item, false, newValue); + } + // get the text that the user manually entered (cut off autocompleted text) + this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length); + // set up ARIA activedescendant + dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id")); + // autocomplete the rest of the option to announce change + this._autoCompleteText(newValue); + }, + + _selectOption: function(/*Event*/ evt){ + // summary: + // Menu callback function, called when an item in the menu is selected. + if(evt){ + this._announceOption(evt.target); + } + this._hideResultList(); + this._setCaretPos(this.focusNode, this.focusNode.value.length); + dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange + }, + + _onArrowMouseDown: function(evt){ + // summary: + // Callback when arrow is clicked + if(this.disabled || this.readOnly){ + return; + } + dojo.stopEvent(evt); + this.focus(); + if(this._isShowingNow){ + this._hideResultList(); + }else{ + // forces full population of results, if they click + // on the arrow it means they want to see more options + this._startSearchAll(); + } + }, + + _startSearchAll: function(){ + this._startSearch(''); + }, + + _startSearchFromInput: function(){ + this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1")); + }, + + _getQueryString: function(/*String*/ text){ + return dojo.string.substitute(this.queryExpr, [text]); + }, + + _startSearch: function(/*String*/ key){ + if(!this._popupWidget){ + var popupId = this.id + "_popup"; + this._popupWidget = new dijit.form._ComboBoxMenu({ + onChange: dojo.hitch(this, this._selectOption), + id: popupId, + dir: this.dir + }); + dijit.removeWaiState(this.focusNode,"activedescendant"); + dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox + } + // create a new query to prevent accidentally querying for a hidden + // value from FilteringSelect's keyField + var query = dojo.clone(this.query); // #5970 + this._lastInput = key; // Store exactly what was entered by the user. + this._lastQuery = query[this.searchAttr] = this._getQueryString(key); + // #5970: set _lastQuery, *then* start the timeout + // otherwise, if the user types and the last query returns before the timeout, + // _lastQuery won't be set and their input gets rewritten + this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){ + this.searchTimer = null; + var fetch = { + queryOptions: { + ignoreCase: this.ignoreCase, + deep: true + }, + query: query, + onBegin: dojo.hitch(this, "_setMaxOptions"), + onComplete: dojo.hitch(this, "_openResultList"), + onError: function(errText){ + _this._fetchHandle = null; + console.error('dijit.form.ComboBox: ' + errText); + dojo.hitch(_this, "_hideResultList")(); + }, + start: 0, + count: this.pageSize + }; + dojo.mixin(fetch, _this.fetchProperties); + this._fetchHandle = _this.store.fetch(fetch); + + var nextSearch = function(dataObject, direction){ + dataObject.start += dataObject.count*direction; + // #4091: + // tell callback the direction of the paging so the screen + // reader knows which menu option to shout + dataObject.direction = direction; + this._fetchHandle = this.store.fetch(dataObject); + }; + this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, this._fetchHandle); + }, query, this), this.searchDelay); + }, + + _setMaxOptions: function(size, request){ + this._maxOptions = size; + }, + + _getValueField: function(){ + // summmary: + // Helper for postMixInProperties() to set this.value based on data inlined into the markup. + // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value. + return this.searchAttr; + }, + + /////////////// Event handlers ///////////////////// + + // FIXME: For 2.0, rename to "_compositionEnd" + 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. + // Note: this event is only triggered in FF (not in IE/safari) + // tags: + // private + + // 229 is the code produced by IE and safari while pressing keys during + // IME input mode + this._onKeyPress({charOrCode: 229}); + }, + + //////////// INITIALIZATION METHODS /////////////////////////////////////// + + constructor: function(){ + this.query={}; + this.fetchProperties={}; + }, + + postMixInProperties: function(){ + if(!this.store){ + var srcNodeRef = this.srcNodeRef; + + // if user didn't specify store, then assume there are option tags + this.store = new dijit.form._ComboBoxDataStore(srcNodeRef); + + // if there is no value set and there is an option list, set + // the value to the first value to be consistent with native + // Select + + // Firefox and Safari set value + // IE6 and Opera set selectedIndex, which is automatically set + // by the selected attribute of an option tag + // IE6 does not set value, Opera sets value = selectedIndex + if(!("value" in this.params)){ + var item = this.store.fetchSelectedItem(); + if(item){ + var valueField = this._getValueField(); + this.value = valueField != this.searchAttr? this.store.getValue(item, valueField) : this.labelFunc(item, this.store); + } + } + } + this.inherited(arguments); + }, + + postCreate: function(){ + // summary: + // Subclasses must call this method from their postCreate() methods + // tags: + // protected + + if(!this.hasDownArrow){ + this.downArrowNode.style.display = "none"; + } + + // find any associated label element and add to ComboBox node. + var label=dojo.query('label[for="'+this.id+'"]'); + if(label.length){ + label[0].id = (this.id+"_label"); + var cn=this.comboNode; + dijit.setWaiState(cn, "labelledby", label[0].id); + + } + this.inherited(arguments); + }, + + uninitialize: function(){ + if(this._popupWidget && !this._popupWidget._destroyed){ + this._hideResultList(); + this._popupWidget.destroy(); + } + this.inherited(arguments); + }, + + _getMenuLabelFromItem: function(/*Item*/ item){ + var label = this.labelAttr? this.store.getValue(item, this.labelAttr) : this.labelFunc(item, this.store); + var labelType = this.labelType; + // If labelType is not "text" we don't want to screw any markup ot whatever. + if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){ + label = this.doHighlight(label, this._escapeHtml(this._lastInput)); + labelType = "html"; + } + return {html: labelType == "html", label: label}; + }, + + doHighlight: function(/*String*/label, /*String*/find){ + // summary: + // Highlights the string entered by the user in the menu. By default this + // highlights the first occurence found. Override this method + // to implement your custom highlighing. + // tags: + // protected + + // Add greedy when this.highlightMatch == "all" + var modifiers = "i"+(this.highlightMatch == "all"?"g":""); + var escapedLabel = this._escapeHtml(label); + find = dojo.regexp.escapeString(find); // escape regexp special chars + var ret = escapedLabel.replace(new RegExp("(^|\\s)("+ find +")", modifiers), + '$1<span class="dijitComboBoxHighlightMatch">$2</span>'); + return ret;// returns String, (almost) valid HTML (entities encoded) + }, + + _escapeHtml: function(/*string*/str){ + // TODO Should become dojo.html.entities(), when exists use instead + // summary: + // Adds escape sequences for special characters in XML: &<>"' + str = String(str).replace(/&/gm, "&").replace(/</gm, "<") + .replace(/>/gm, ">").replace(/"/gm, """); + return str; // string + }, + + open: function(){ + // summary: + // Opens the drop down menu. TODO: rename to _open. + // tags: + // private + this._isShowingNow=true; + return dijit.popup.open({ + popup: this._popupWidget, + around: this.domNode, + parent: this + }); + }, + + reset: function(){ + // Overrides the _FormWidget.reset(). + // Additionally reset the .item (to clean up). + this.item = null; + this.inherited(arguments); + }, + + labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){ + // summary: + // Computes the label to display based on the dojo.data store item. + // returns: + // The label that the ComboBox should display + // tags: + // private + + // Use toString() because XMLStore returns an XMLItem whereas this + // method is expected to return a String (#9354) + return store.getValue(item, this.searchAttr).toString(); // String + } + } +); + +dojo.declare( + "dijit.form._ComboBoxMenu", + [dijit._Widget, dijit._Templated, dijit._CssStateMixin], + { + // summary: + // Focus-less menu for internal use in `dijit.form.ComboBox` + // tags: + // private + + templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' tabIndex='-1' style='overflow: \"auto\"; overflow-x: \"hidden\";'>" + +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' waiRole='option'></li>" + +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' waiRole='option'></li>" + +"</ul>", + + // _messages: Object + // Holds "next" and "previous" text for paging buttons on drop down + _messages: null, + + baseClass: "dijitComboBoxMenu", + + postMixInProperties: function(){ + this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang); + this.inherited(arguments); + }, + + _setValueAttr: function(/*Object*/ value){ + this.value = value; + this.onChange(value); + }, + + // stubs + onChange: function(/*Object*/ value){ + // summary: + // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu. + // Probably should be called onSelect. + // tags: + // callback + }, + onPage: function(/*Number*/ direction){ + // summary: + // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page. + // tags: + // callback + }, + + postCreate: function(){ + // fill in template with i18n messages + this.previousButton.innerHTML = this._messages["previousMessage"]; + this.nextButton.innerHTML = this._messages["nextMessage"]; + this.inherited(arguments); + }, + + onClose: function(){ + // summary: + // Callback from dijit.popup code to this widget, notifying it that it closed + // tags: + // private + this._blurOptionNode(); + }, + + _createOption: function(/*Object*/ item, labelFunc){ + // summary: + // Creates an option to appear on the popup menu subclassed by + // `dijit.form.FilteringSelect`. + + var labelObject = labelFunc(item); + var menuitem = dojo.doc.createElement("li"); + dijit.setWaiRole(menuitem, "option"); + if(labelObject.html){ + menuitem.innerHTML = labelObject.label; + }else{ + menuitem.appendChild( + dojo.doc.createTextNode(labelObject.label) + ); + } + // #3250: in blank options, assign a normal height + if(menuitem.innerHTML == ""){ + menuitem.innerHTML = " "; + } + menuitem.item=item; + return menuitem; + }, + + createOptions: function(results, dataObject, labelFunc){ + // summary: + // Fills in the items in the drop down list + // results: + // Array of dojo.data items + // dataObject: + // dojo.data store + // labelFunc: + // Function to produce a label in the drop down list from a dojo.data item + + //this._dataObject=dataObject; + //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList); + // display "Previous . . ." button + this.previousButton.style.display = (dataObject.start == 0) ? "none" : ""; + dojo.attr(this.previousButton, "id", this.id + "_prev"); + // create options using _createOption function defined by parent + // ComboBox (or FilteringSelect) class + // #2309: + // iterate over cache nondestructively + dojo.forEach(results, function(item, i){ + var menuitem = this._createOption(item, labelFunc); + menuitem.className = "dijitReset dijitMenuItem" + + (this.isLeftToRight() ? "" : " dijitMenuItemRtl"); + dojo.attr(menuitem, "id", this.id + i); + this.domNode.insertBefore(menuitem, this.nextButton); + }, this); + // display "Next . . ." button + var displayMore = false; + //Try to determine if we should show 'more'... + if(dataObject._maxOptions && dataObject._maxOptions != -1){ + if((dataObject.start + dataObject.count) < dataObject._maxOptions){ + displayMore = true; + }else if((dataObject.start + dataObject.count) > dataObject._maxOptions && dataObject.count == results.length){ + //Weird return from a datastore, where a start + count > maxOptions + // implies maxOptions isn't really valid and we have to go into faking it. + //And more or less assume more if count == results.length + displayMore = true; + } + }else if(dataObject.count == results.length){ + //Don't know the size, so we do the best we can based off count alone. + //So, if we have an exact match to count, assume more. + displayMore = true; + } + + this.nextButton.style.display = displayMore ? "" : "none"; + dojo.attr(this.nextButton,"id", this.id + "_next"); + return this.domNode.childNodes; + }, + + clearResultList: function(){ + // summary: + // Clears the entries in the drop down list, but of course keeps the previous and next buttons. + while(this.domNode.childNodes.length>2){ + this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]); + } + }, + + _onMouseDown: function(/*Event*/ evt){ + dojo.stopEvent(evt); + }, + + _onMouseUp: function(/*Event*/ evt){ + if(evt.target === this.domNode || !this._highlighted_option){ + return; + }else if(evt.target == this.previousButton){ + this.onPage(-1); + }else if(evt.target == this.nextButton){ + this.onPage(1); + }else{ + var tgt = evt.target; + // while the clicked node is inside the div + while(!tgt.item){ + // recurse to the top + tgt = tgt.parentNode; + } + this._setValueAttr({ target: tgt }, true); + } + }, + + _onMouseOver: function(/*Event*/ evt){ + if(evt.target === this.domNode){ return; } + var tgt = evt.target; + if(!(tgt == this.previousButton || tgt == this.nextButton)){ + // while the clicked node is inside the div + while(!tgt.item){ + // recurse to the top + tgt = tgt.parentNode; + } + } + this._focusOptionNode(tgt); + }, + + _onMouseOut: function(/*Event*/ evt){ + if(evt.target === this.domNode){ return; } + this._blurOptionNode(); + }, + + _focusOptionNode: function(/*DomNode*/ node){ + // summary: + // Does the actual highlight. + if(this._highlighted_option != node){ + this._blurOptionNode(); + this._highlighted_option = node; + dojo.addClass(this._highlighted_option, "dijitMenuItemSelected"); + } + }, + + _blurOptionNode: function(){ + // summary: + // Removes highlight on highlighted option. + if(this._highlighted_option){ + dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected"); + this._highlighted_option = null; + } + }, + + _highlightNextOption: function(){ + // summary: + // Highlight the item just below the current selection. + // If nothing selected, highlight first option. + + // because each press of a button clears the menu, + // the highlighted option sometimes becomes detached from the menu! + // test to see if the option has a parent to see if this is the case. + if(!this.getHighlightedOption()){ + var fc = this.domNode.firstChild; + this._focusOptionNode(fc.style.display == "none" ? fc.nextSibling : fc); + }else{ + var ns = this._highlighted_option.nextSibling; + if(ns && ns.style.display != "none"){ + this._focusOptionNode(ns); + }else{ + this.highlightFirstOption(); + } + } + // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover + dojo.window.scrollIntoView(this._highlighted_option); + }, + + highlightFirstOption: function(){ + // summary: + // Highlight the first real item in the list (not Previous Choices). + var first = this.domNode.firstChild; + var second = first.nextSibling; + this._focusOptionNode(second.style.display == "none" ? first : second); // remotely possible that Previous Choices is the only thing in the list + dojo.window.scrollIntoView(this._highlighted_option); + }, + + highlightLastOption: function(){ + // summary: + // Highlight the last real item in the list (not More Choices). + this._focusOptionNode(this.domNode.lastChild.previousSibling); + dojo.window.scrollIntoView(this._highlighted_option); + }, + + _highlightPrevOption: function(){ + // summary: + // Highlight the item just above the current selection. + // If nothing selected, highlight last option (if + // you select Previous and try to keep scrolling up the list). + if(!this.getHighlightedOption()){ + var lc = this.domNode.lastChild; + this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc); + }else{ + var ps = this._highlighted_option.previousSibling; + if(ps && ps.style.display != "none"){ + this._focusOptionNode(ps); + }else{ + this.highlightLastOption(); + } + } + dojo.window.scrollIntoView(this._highlighted_option); + }, + + _page: function(/*Boolean*/ up){ + // summary: + // Handles page-up and page-down keypresses + + var scrollamount = 0; + var oldscroll = this.domNode.scrollTop; + var height = dojo.style(this.domNode, "height"); + // if no item is highlighted, highlight the first option + if(!this.getHighlightedOption()){ + this._highlightNextOption(); + } + while(scrollamount<height){ + if(up){ + // stop at option 1 + if(!this.getHighlightedOption().previousSibling || + this._highlighted_option.previousSibling.style.display == "none"){ + break; + } + this._highlightPrevOption(); + }else{ + // stop at last option + if(!this.getHighlightedOption().nextSibling || + this._highlighted_option.nextSibling.style.display == "none"){ + break; + } + this._highlightNextOption(); + } + // going backwards + var newscroll=this.domNode.scrollTop; + scrollamount+=(newscroll-oldscroll)*(up ? -1:1); + oldscroll=newscroll; + } + }, + + pageUp: function(){ + // summary: + // Handles pageup keypress. + // TODO: just call _page directly from handleKey(). + // tags: + // private + this._page(true); + }, + + pageDown: function(){ + // summary: + // Handles pagedown keypress. + // TODO: just call _page directly from handleKey(). + // tags: + // private + this._page(false); + }, + + getHighlightedOption: function(){ + // summary: + // Returns the highlighted option. + var ho = this._highlighted_option; + return (ho && ho.parentNode) ? ho : null; + }, + + handleKey: function(key){ + switch(key){ + case dojo.keys.DOWN_ARROW: + this._highlightNextOption(); + break; + case dojo.keys.PAGE_DOWN: + this.pageDown(); + break; + case dojo.keys.UP_ARROW: + this._highlightPrevOption(); + break; + case dojo.keys.PAGE_UP: + this.pageUp(); + break; + } + } + } +); + +dojo.declare( + "dijit.form.ComboBox", + [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin], + { + // summary: + // Auto-completing text box, and base class for dijit.form.FilteringSelect. + // + // description: + // 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. + // If OPTION tags are used as the data provider via markup, + // then the OPTION tag's child text node is used as the widget value + // when selected. The OPTION tag's value attribute is ignored. + // To set the default value when using OPTION tags, specify the selected + // attribute on 1 of the child OPTION tags. + // + // Some of the options to the ComboBox are actually arguments to the data + // provider. + + _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Hook so attr('value', value) works. + // description: + // Sets the value of the select. + this.item = null; // value not looked up in store + if(!value){ value = ''; } // null translates to blank + dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue); + } + } +); + +dojo.declare("dijit.form._ComboBoxDataStore", null, { + // summary: + // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data + // + // description: + // Provides a store for inlined data like: + // + // | <select> + // | <option value="AL">Alabama</option> + // | ... + // + // Actually. just implements the subset of dojo.data.Read/Notification + // needed for ComboBox and FilteringSelect to work. + // + // Note that an item is just a pointer to the <option> DomNode. + + constructor: function( /*DomNode*/ root){ + this.root = root; + if(root.tagName != "SELECT" && root.firstChild){ + root = dojo.query("select", root); + if(root.length > 0){ // SELECT is a child of srcNodeRef + root = root[0]; + }else{ // no select, so create 1 to parent the option tags to define selectedIndex + this.root.innerHTML = "<SELECT>"+this.root.innerHTML+"</SELECT>"; + root = this.root.firstChild; + } + this.root = root; + } + dojo.query("> option", root).forEach(function(node){ + // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be. + // If it is needed then can we just hide the select itself instead? + //node.style.display="none"; + node.innerHTML = dojo.trim(node.innerHTML); + }); + + }, + + getValue: function( /* item */ item, + /* attribute-name-string */ attribute, + /* value? */ defaultValue){ + return (attribute == "value") ? item.value : (item.innerText || item.textContent || ''); + }, + + isItemLoaded: function(/* anything */ something){ + return true; + }, + + getFeatures: function(){ + return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true}; + }, + + _fetchItems: function( /* Object */ args, + /* Function */ findCallback, + /* Function */ errorCallback){ + // summary: + // See dojo.data.util.simpleFetch.fetch() + if(!args.query){ args.query = {}; } + if(!args.query.name){ args.query.name = ""; } + if(!args.queryOptions){ args.queryOptions = {}; } + var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase), + items = dojo.query("> option", this.root).filter(function(option){ + return (option.innerText || option.textContent || '').match(matcher); + } ); + if(args.sort){ + items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this)); + } + findCallback(items, args); + }, + + close: function(/*dojo.data.api.Request || args || null */ request){ + return; + }, + + getLabel: function(/* item */ item){ + return item.innerHTML; + }, + + getIdentity: function(/* item */ item){ + return dojo.attr(item, "value"); + }, + + fetchItemByIdentity: function(/* Object */ args){ + // summary: + // Given the identity of an item, this method returns the item that has + // that identity through the onItem callback. + // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details. + // + // description: + // Given arguments like: + // + // | {identity: "CA", onItem: function(item){...} + // + // Call `onItem()` with the DOM node `<option value="CA">California</option>` + var item = dojo.query("> option[value='" + args.identity + "']", this.root)[0]; + args.onItem(item); + }, + + fetchSelectedItem: function(){ + // summary: + // Get the option marked as selected, like `<option selected>`. + // Not part of dojo.data API. + var root = this.root, + si = root.selectedIndex; + return typeof si == "number" + ? dojo.query("> option:nth-child(" + (si != -1 ? si+1 : 1) + ")", root)[0] + : null; // dojo.data.Item + } +}); +//Mix in the simple fetch implementation to this class. +dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch); + +} + +if(!dojo._hasResource["dijit.form.FilteringSelect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.FilteringSelect"] = true; +dojo.provide("dijit.form.FilteringSelect"); + + + +dojo.declare( + "dijit.form.FilteringSelect", + [dijit.form.MappedTextBox, dijit.form.ComboBoxMixin], + { + // summary: + // An enhanced version of the HTML SELECT tag, populated dynamically + // + // description: + // An enhanced version of the HTML SELECT tag, populated dynamically. It works + // very nicely with very large data sets because it can load and page data as needed. + // It also resembles ComboBox, but does not allow values outside of the provided ones. + // If OPTION tags are used as the data provider via markup, then the + // OPTION tag's child text node is used as the displayed value when selected + // while the OPTION tag's value attribute is used as the widget value on form submit. + // To set the default value when using OPTION tags, specify the selected + // attribute on 1 of the child OPTION tags. + // + // Similar features: + // - There is a drop down list of possible values. + // - You can only enter a value from the drop down list. (You can't + // enter an arbitrary value.) + // - The value submitted with the form is the hidden value (ex: CA), + // not the displayed value a.k.a. label (ex: California) + // + // Enhancements over plain HTML version: + // - If you type in some text then it will filter down the list of + // possible values in the drop down list. + // - List can be specified either as a static list or via a javascript + // function (that can get the list from a server) + + _isvalid: true, + + // required: Boolean + // True (default) if user is required to enter a value into this field. + required: true, + + _lastDisplayedValue: "", + + isValid: function(){ + // Overrides ValidationTextBox.isValid() + return this._isvalid || (!this.required && this.get('displayedValue') == ""); // #5974 + }, + + _refreshState: function(){ + if(!this.searchTimer){ // state will be refreshed after results are returned + this.inherited(arguments); + } + }, + + _callbackSetLabel: function( /*Array*/ result, + /*Object*/ dataObject, + /*Boolean?*/ priorityChange){ + // summary: + // Callback function that dynamically sets the label of the + // ComboBox + + // setValue does a synchronous lookup, + // so it calls _callbackSetLabel directly, + // and so does not pass dataObject + // still need to test against _lastQuery in case it came too late + if((dataObject && dataObject.query[this.searchAttr] != this._lastQuery) || (!dataObject && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){ + return; + } + if(!result.length){ + //#3268: do nothing on bad input + //#3285: change CSS to indicate error + this.valueNode.value = ""; + dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange === undefined && !this._focused)); + this._isvalid = false; + this.validate(this._focused); + this.item = null; + }else{ + this.set('item', result[0], priorityChange); + } + }, + + _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ + // Overrides ComboBox._openResultList() + + // #3285: tap into search callback to see if user's query resembles a match + if(dataObject.query[this.searchAttr] != this._lastQuery){ + return; + } + if(this.item === undefined){ // item == undefined for keyboard search + this._isvalid = results.length != 0 || this._maxOptions != 0; // result.length==0 && maxOptions != 0 implies the nextChoices item selected but then the datastore returned 0 more entries + this.validate(true); + } + dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments); + }, + + _getValueAttr: function(){ + // summary: + // Hook for attr('value') to work. + + // don't get the textbox value but rather the previously set hidden value. + // Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur + return this.valueNode.value; + }, + + _getValueField: function(){ + // Overrides ComboBox._getValueField() + return "value"; + }, + + _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){ + // summary: + // Hook so attr('value', value) works. + // description: + // Sets the value of the select. + // Also sets the label to the corresponding value by reverse lookup. + if(!this._onChangeActive){ priorityChange = null; } + this._lastQuery = value; + + if(value === null || value === ''){ + this._setDisplayedValueAttr('', priorityChange); + return; + } + + //#3347: fetchItemByIdentity if no keyAttr specified + var self = this; + this.store.fetchItemByIdentity({ + identity: value, + onItem: function(item){ + self._callbackSetLabel(item? [item] : [], undefined, priorityChange); + } + }); + }, + + _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Set the displayed valued in the input box, and the hidden value + // that gets submitted, based on a dojo.data store item. + // description: + // Users shouldn't call this function; they should be calling + // attr('item', value) + // tags: + // private + this._isvalid = true; + this.inherited(arguments); + this.valueNode.value = this.value; + this._lastDisplayedValue = this.textbox.value; + }, + + _getDisplayQueryString: function(/*String*/ text){ + return text.replace(/([\\\*\?])/g, "\\$1"); + }, + + _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){ + // summary: + // Hook so attr('displayedValue', label) works. + // description: + // Sets textbox to display label. Also performs reverse lookup + // to set the hidden value. + + // When this is called during initialization it'll ping the datastore + // for reverse lookup, and when that completes (after an XHR request) + // will call setValueAttr()... but that shouldn't trigger an onChange() + // event, even when it happens after creation has finished + if(!this._created){ + priorityChange = false; + } + + if(this.store){ + this._hideResultList(); + var query = dojo.clone(this.query); // #6196: populate query with user-specifics + // escape meta characters of dojo.data.util.filter.patternToRegExp(). + this._lastQuery = query[this.searchAttr] = this._getDisplayQueryString(label); + // if the label is not valid, the callback will never set it, + // so the last valid value will get the warning textbox set the + // textbox value now so that the impending warning will make + // sense to the user + this.textbox.value = label; + this._lastDisplayedValue = label; + var _this = this; + var fetch = { + query: query, + queryOptions: { + ignoreCase: this.ignoreCase, + deep: true + }, + onComplete: function(result, dataObject){ + _this._fetchHandle = null; + dojo.hitch(_this, "_callbackSetLabel")(result, dataObject, priorityChange); + }, + onError: function(errText){ + _this._fetchHandle = null; + console.error('dijit.form.FilteringSelect: ' + errText); + dojo.hitch(_this, "_callbackSetLabel")([], undefined, false); + } + }; + dojo.mixin(fetch, this.fetchProperties); + this._fetchHandle = this.store.fetch(fetch); + } + }, + + postMixInProperties: function(){ + this.inherited(arguments); + this._isvalid = !this.required; + }, + + undo: function(){ + this.set('displayedValue', this._lastDisplayedValue); + } + } +); + +} + +if(!dojo._hasResource["dijit.form.Form"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.Form"] = true; +dojo.provide("dijit.form.Form"); + + + + + +dojo.declare( + "dijit.form.Form", + [dijit._Widget, dijit._Templated, dijit.form._FormMixin], + { + // summary: + // Widget corresponding to HTML form tag, for validation and serialization + // + // example: + // | <form dojoType="dijit.form.Form" id="myForm"> + // | Name: <input type="text" name="name" /> + // | </form> + // | myObj = {name: "John Doe"}; + // | dijit.byId('myForm').set('value', myObj); + // | + // | myObj=dijit.byId('myForm').get('value'); + + // HTML <FORM> attributes + + // name: String? + // Name of form for scripting. + name: "", + + // action: String? + // Server-side form handler. + action: "", + + // method: String? + // HTTP method used to submit the form, either "GET" or "POST". + method: "", + + // encType: String? + // Encoding type for the form, ex: application/x-www-form-urlencoded. + encType: "", + + // accept-charset: String? + // List of supported charsets. + "accept-charset": "", + + // accept: String? + // List of MIME types for file upload. + accept: "", + + // target: String? + // Target frame for the document to be opened in. + target: "", + + templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}></form>", + + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + action: "", + method: "", + encType: "", + "accept-charset": "", + accept: "", + target: "" + }), + + postMixInProperties: function(){ + // Setup name=foo string to be referenced from the template (but only if a name has been specified) + // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660 + this.nameAttrSetting = this.name ? ("name='" + this.name + "'") : ""; + this.inherited(arguments); + }, + + execute: function(/*Object*/ formContents){ + // summary: + // Deprecated: use submit() + // tags: + // deprecated + }, + + onExecute: function(){ + // summary: + // Deprecated: use onSubmit() + // tags: + // deprecated + }, + + _setEncTypeAttr: function(/*String*/ value){ + this.encType = value; + dojo.attr(this.domNode, "encType", value); + if(dojo.isIE){ this.domNode.encoding = value; } + }, + + postCreate: function(){ + // IE tries to hide encType + // TODO: this code should be in parser, not here. + if(dojo.isIE && this.srcNodeRef && this.srcNodeRef.attributes){ + var item = this.srcNodeRef.attributes.getNamedItem('encType'); + if(item && !item.specified && (typeof item.value == "string")){ + this.set('encType', item.value); + } + } + this.inherited(arguments); + }, + + reset: function(/*Event?*/ e){ + // summary: + // restores all widget values back to their init values, + // calls onReset() which can cancel the reset by returning false + + // create fake event so we can know if preventDefault() is called + var faux = { + returnValue: true, // the IE way + preventDefault: function(){ // not IE + this.returnValue = false; + }, + stopPropagation: function(){}, + currentTarget: e ? e.target : this.domNode, + target: e ? e.target : this.domNode + }; + // if return value is not exactly false, and haven't called preventDefault(), then reset + if(!(this.onReset(faux) === false) && faux.returnValue){ + this.inherited(arguments, []); + } + }, + + onReset: function(/*Event?*/ e){ + // summary: + // Callback when user resets the form. This method is intended + // to be over-ridden. When the `reset` method is called + // programmatically, the return value from `onReset` is used + // to compute whether or not resetting should proceed + // tags: + // callback + return true; // Boolean + }, + + _onReset: function(e){ + this.reset(e); + dojo.stopEvent(e); + return false; + }, + + _onSubmit: function(e){ + var fp = dijit.form.Form.prototype; + // TODO: remove this if statement beginning with 2.0 + if(this.execute != fp.execute || this.onExecute != fp.onExecute){ + dojo.deprecated("dijit.form.Form:execute()/onExecute() are deprecated. Use onSubmit() instead.", "", "2.0"); + this.onExecute(); + this.execute(this.getValues()); + } + if(this.onSubmit(e) === false){ // only exactly false stops submit + dojo.stopEvent(e); + } + }, + + onSubmit: function(/*Event?*/e){ + // summary: + // Callback when user submits the form. + // description: + // This method is intended to be over-ridden, but by default it checks and + // returns the validity of form elements. When the `submit` + // method is called programmatically, the return value from + // `onSubmit` is used to compute whether or not submission + // should proceed + // tags: + // extension + + return this.isValid(); // Boolean + }, + + submit: function(){ + // summary: + // programmatically submit form if and only if the `onSubmit` returns true + if(!(this.onSubmit() === false)){ + this.containerNode.submit(); + } + } + } +); + +} + +if(!dojo._hasResource["dijit.form.RadioButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.RadioButton"] = true; +dojo.provide("dijit.form.RadioButton"); + + +// TODO: for 2.0, move the RadioButton code into this file + +} + +if(!dojo._hasResource["dijit.form._FormSelectWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form._FormSelectWidget"] = true; +dojo.provide("dijit.form._FormSelectWidget"); + + + + +/*===== +dijit.form.__SelectOption = function(){ + // value: String + // The value of the option. Setting to empty (or missing) will + // place a separator at that location + // label: String + // The label for our option. It can contain html tags. + // selected: Boolean + // Whether or not we are a selected option + // disabled: Boolean + // Whether or not this specific option is disabled + this.value = value; + this.label = label; + this.selected = selected; + this.disabled = disabled; +} +=====*/ + +dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { + // summary: + // Extends _FormValueWidget in order to provide "select-specific" + // values - i.e., those values that are unique to <select> elements. + // This also provides the mechanism for reading the elements from + // a store, if desired. + + // multiple: Boolean + // Whether or not we are multi-valued + multiple: false, + + // options: dijit.form.__SelectOption[] + // The set of options for our select item. Roughly corresponds to + // the html <option> tag. + options: null, + + // store: dojo.data.api.Identity + // A store which, at the very least impelements dojo.data.api.Identity + // to use for getting our list of options - rather than reading them + // from the <option> html tags. + store: null, + + // query: object + // A query to use when fetching items from our store + query: null, + + // queryOptions: object + // Query options to use when fetching from the store + queryOptions: null, + + // onFetch: Function + // A callback to do with an onFetch - but before any items are actually + // iterated over (i.e. to filter even futher what you want to add) + onFetch: null, + + // sortByLabel: boolean + // Flag to sort the options returned from a store by the label of + // the store. + sortByLabel: true, + + + // loadChildrenOnOpen: boolean + // By default loadChildren is called when the items are fetched from the + // store. This property allows delaying loadChildren (and the creation + // of the options/menuitems) until the user opens the click the button. + // dropdown + loadChildrenOnOpen: false, + + getOptions: function(/* anything */ valueOrIdx){ + // summary: + // Returns a given option (or options). + // valueOrIdx: + // If passed in as a string, that string is used to look up the option + // in the array of options - based on the value property. + // (See dijit.form.__SelectOption). + // + // If passed in a number, then the option with the given index (0-based) + // within this select will be returned. + // + // If passed in a dijit.form.__SelectOption, the same option will be + // returned if and only if it exists within this select. + // + // If passed an array, then an array will be returned with each element + // in the array being looked up. + // + // If not passed a value, then all options will be returned + // + // returns: + // The option corresponding with the given value or index. null + // is returned if any of the following are true: + // - A string value is passed in which doesn't exist + // - An index is passed in which is outside the bounds of the array of options + // - A dijit.form.__SelectOption is passed in which is not a part of the select + + // NOTE: the compare for passing in a dijit.form.__SelectOption checks + // if the value property matches - NOT if the exact option exists + // NOTE: if passing in an array, null elements will be placed in the returned + // array when a value is not found. + var lookupValue = valueOrIdx, opts = this.options || [], l = opts.length; + + if(lookupValue === undefined){ + return opts; // dijit.form.__SelectOption[] + } + if(dojo.isArray(lookupValue)){ + return dojo.map(lookupValue, "return this.getOptions(item);", this); // dijit.form.__SelectOption[] + } + if(dojo.isObject(valueOrIdx)){ + // We were passed an option - so see if it's in our array (directly), + // and if it's not, try and find it by value. + if(!dojo.some(this.options, function(o, idx){ + if(o === lookupValue || + (o.value && o.value === lookupValue.value)){ + lookupValue = idx; + return true; + } + return false; + })){ + lookupValue = -1; + } + } + if(typeof lookupValue == "string"){ + for(var i=0; i<l; i++){ + if(opts[i].value === lookupValue){ + lookupValue = i; + break; + } + } + } + if(typeof lookupValue == "number" && lookupValue >= 0 && lookupValue < l){ + return this.options[lookupValue] // dijit.form.__SelectOption + } + return null; // null + }, + + addOption: function(/* dijit.form.__SelectOption, dijit.form.__SelectOption[] */ option){ + // summary: + // Adds an option or options to the end of the select. If value + // of the option is empty or missing, a separator is created instead. + // Passing in an array of options will yield slightly better performance + // since the children are only loaded once. + if(!dojo.isArray(option)){ option = [option]; } + dojo.forEach(option, function(i){ + if(i && dojo.isObject(i)){ + this.options.push(i); + } + }, this); + this._loadChildren(); + }, + + removeOption: function(/* string, dijit.form.__SelectOption, number, or array */ valueOrIdx){ + // summary: + // Removes the given option or options. You can remove by string + // (in which case the value is removed), number (in which case the + // index in the options array is removed), or select option (in + // which case, the select option with a matching value is removed). + // You can also pass in an array of those values for a slightly + // better performance since the children are only loaded once. + if(!dojo.isArray(valueOrIdx)){ valueOrIdx = [valueOrIdx]; } + var oldOpts = this.getOptions(valueOrIdx); + dojo.forEach(oldOpts, function(i){ + // We can get null back in our array - if our option was not found. In + // that case, we don't want to blow up... + if(i){ + this.options = dojo.filter(this.options, function(node, idx){ + return (node.value !== i.value); + }); + this._removeOptionItem(i); + } + }, this); + this._loadChildren(); + }, + + updateOption: function(/* dijit.form.__SelectOption, dijit.form.__SelectOption[] */ newOption){ + // summary: + // Updates the values of the given option. The option to update + // is matched based on the value of the entered option. Passing + // in an array of new options will yeild better performance since + // the children will only be loaded once. + if(!dojo.isArray(newOption)){ newOption = [newOption]; } + dojo.forEach(newOption, function(i){ + var oldOpt = this.getOptions(i), k; + if(oldOpt){ + for(k in i){ oldOpt[k] = i[k]; } + } + }, this); + this._loadChildren(); + }, + + setStore: function(/* dojo.data.api.Identity */ store, + /* anything? */ selectedValue, + /* Object? */ fetchArgs){ + // summary: + // Sets the store you would like to use with this select widget. + // The selected value is the value of the new store to set. This + // function returns the original store, in case you want to reuse + // it or something. + // store: dojo.data.api.Identity + // The store you would like to use - it MUST implement Identity, + // and MAY implement Notification. + // selectedValue: anything? + // The value that this widget should set itself to *after* the store + // has been loaded + // fetchArgs: Object? + // The arguments that will be passed to the store's fetch() function + var oStore = this.store; + fetchArgs = fetchArgs || {}; + if(oStore !== store){ + // Our store has changed, so update our notifications + dojo.forEach(this._notifyConnections || [], dojo.disconnect); + delete this._notifyConnections; + if(store && store.getFeatures()["dojo.data.api.Notification"]){ + this._notifyConnections = [ + dojo.connect(store, "onNew", this, "_onNewItem"), + dojo.connect(store, "onDelete", this, "_onDeleteItem"), + dojo.connect(store, "onSet", this, "_onSetItem") + ]; + } + this.store = store; + } + + // Turn off change notifications while we make all these changes + this._onChangeActive = false; + + // Remove existing options (if there are any) + if(this.options && this.options.length){ + this.removeOption(this.options); + } + + // Add our new options + if(store){ + var cb = function(items){ + if(this.sortByLabel && !fetchArgs.sort && items.length){ + items.sort(dojo.data.util.sorter.createSortFunction([{ + attribute: store.getLabelAttributes(items[0])[0] + }], store)); + } + + if(fetchArgs.onFetch){ + items = fetchArgs.onFetch(items); + } + // TODO: Add these guys as a batch, instead of separately + dojo.forEach(items, function(i){ + this._addOptionForItem(i); + }, this); + + // Set our value (which might be undefined), and then tweak + // it to send a change event with the real value + this._loadingStore = false; + this.set("value", (("_pendingValue" in this) ? this._pendingValue : selectedValue)); + delete this._pendingValue; + + if(!this.loadChildrenOnOpen){ + this._loadChildren(); + }else{ + this._pseudoLoadChildren(items); + } + this._fetchedWith = opts; + this._lastValueReported = this.multiple ? [] : null; + this._onChangeActive = true; + this.onSetStore(); + this._handleOnChange(this.value); + }; + var opts = dojo.mixin({onComplete:cb, scope: this}, fetchArgs); + this._loadingStore = true; + store.fetch(opts); + }else{ + delete this._fetchedWith; + } + return oStore; // dojo.data.api.Identity + }, + + _setValueAttr: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){ + // summary: + // set the value of the widget. + // If a string is passed, then we set our value from looking it up. + if(this._loadingStore){ + // Our store is loading - so save our value, and we'll set it when + // we're done + this._pendingValue = newValue; + return; + } + var opts = this.getOptions() || []; + if(!dojo.isArray(newValue)){ + newValue = [newValue]; + } + dojo.forEach(newValue, function(i, idx){ + if(!dojo.isObject(i)){ + i = i + ""; + } + if(typeof i === "string"){ + newValue[idx] = dojo.filter(opts, function(node){ + return node.value === i; + })[0] || {value: "", label: ""}; + } + }, this); + + // Make sure some sane default is set + newValue = dojo.filter(newValue, function(i){ return i && i.value; }); + if(!this.multiple && (!newValue[0] || !newValue[0].value) && opts.length){ + newValue[0] = opts[0]; + } + dojo.forEach(opts, function(i){ + i.selected = dojo.some(newValue, function(v){ return v.value === i.value; }); + }); + var val = dojo.map(newValue, function(i){ return i.value; }), + disp = dojo.map(newValue, function(i){ return i.label; }); + + this.value = this.multiple ? val : val[0]; + this._setDisplay(this.multiple ? disp : disp[0]); + this._updateSelection(); + this._handleOnChange(this.value, priorityChange); + }, + + _getDisplayedValueAttr: function(){ + // summary: + // returns the displayed value of the widget + var val = this.get("value"); + if(!dojo.isArray(val)){ + val = [val]; + } + var ret = dojo.map(this.getOptions(val), function(v){ + if(v && "label" in v){ + return v.label; + }else if(v){ + return v.value; + } + return null; + }, this); + return this.multiple ? ret : ret[0]; + }, + + _getValueDeprecated: false, // remove when _FormWidget:getValue is removed + getValue: function(){ + // summary: + // get the value of the widget. + return this._lastValue; + }, + + undo: function(){ + // summary: + // restore the value to the last value passed to onChange + this._setValueAttr(this._lastValueReported, false); + }, + + _loadChildren: function(){ + // summary: + // Loads the children represented by this widget's options. + // reset the menu to make it "populatable on the next click + if(this._loadingStore){ return; } + dojo.forEach(this._getChildren(), function(child){ + child.destroyRecursive(); + }); + // Add each menu item + dojo.forEach(this.options, this._addOptionItem, this); + + // Update states + this._updateSelection(); + }, + + _updateSelection: function(){ + // summary: + // Sets the "selected" class on the item for styling purposes + this.value = this._getValueFromOpts(); + var val = this.value; + if(!dojo.isArray(val)){ + val = [val]; + } + if(val && val[0]){ + dojo.forEach(this._getChildren(), function(child){ + var isSelected = dojo.some(val, function(v){ + return child.option && (v === child.option.value); + }); + dojo.toggleClass(child.domNode, this.baseClass + "SelectedOption", isSelected); + dijit.setWaiState(child.domNode, "selected", isSelected); + }, this); + } + this._handleOnChange(this.value); + }, + + _getValueFromOpts: function(){ + // summary: + // Returns the value of the widget by reading the options for + // the selected flag + var opts = this.getOptions() || []; + if(!this.multiple && opts.length){ + // Mirror what a select does - choose the first one + var opt = dojo.filter(opts, function(i){ + return i.selected; + })[0]; + if(opt && opt.value){ + return opt.value + }else{ + opts[0].selected = true; + return opts[0].value; + } + }else if(this.multiple){ + // Set value to be the sum of all selected + return dojo.map(dojo.filter(opts, function(i){ + return i.selected; + }), function(i){ + return i.value; + }) || []; + } + return ""; + }, + + // Internal functions to call when we have store notifications come in + _onNewItem: function(/* item */ item, /* Object? */ parentInfo){ + if(!parentInfo || !parentInfo.parent){ + // Only add it if we are top-level + this._addOptionForItem(item); + } + }, + _onDeleteItem: function(/* item */ item){ + var store = this.store; + this.removeOption(store.getIdentity(item)); + }, + _onSetItem: function(/* item */ item){ + this.updateOption(this._getOptionObjForItem(item)); + }, + + _getOptionObjForItem: function(item){ + // summary: + // Returns an option object based off the given item. The "value" + // of the option item will be the identity of the item, the "label" + // of the option will be the label of the item. If the item contains + // children, the children value of the item will be set + var store = this.store, label = store.getLabel(item), + value = (label ? store.getIdentity(item) : null); + return {value: value, label: label, item:item}; // dijit.form.__SelectOption + }, + + _addOptionForItem: function(/* item */ item){ + // summary: + // Creates (and adds) the option for the given item + var store = this.store; + if(!store.isItemLoaded(item)){ + // We are not loaded - so let's load it and add later + store.loadItem({item: item, onComplete: function(i){ + this._addOptionForItem(item); + }, + scope: this}); + return; + } + var newOpt = this._getOptionObjForItem(item); + this.addOption(newOpt); + }, + + constructor: function(/* Object */ keywordArgs){ + // summary: + // Saves off our value, if we have an initial one set so we + // can use it if we have a store as well (see startup()) + this._oValue = (keywordArgs || {}).value || null; + }, + + _fillContent: function(){ + // summary: + // Loads our options and sets up our dropdown correctly. We + // don't want any content, so we don't call any inherit chain + // function. + var opts = this.options; + if(!opts){ + opts = this.options = this.srcNodeRef ? dojo.query(">", + this.srcNodeRef).map(function(node){ + if(node.getAttribute("type") === "separator"){ + return { value: "", label: "", selected: false, disabled: false }; + } + return { value: node.getAttribute("value"), + label: String(node.innerHTML), + selected: node.getAttribute("selected") || false, + disabled: node.getAttribute("disabled") || false }; + }, this) : []; + } + if(!this.value){ + this.value = this._getValueFromOpts(); + }else if(this.multiple && typeof this.value == "string"){ + this.value = this.value.split(","); + } + }, + + postCreate: function(){ + // summary: + // sets up our event handling that we need for functioning + // as a select + dojo.setSelectable(this.focusNode, false); + this.inherited(arguments); + + // Make our event connections for updating state + this.connect(this, "onChange", "_updateSelection"); + this.connect(this, "startup", "_loadChildren"); + + this._setValueAttr(this.value, null); + }, + + startup: function(){ + // summary: + // Connects in our store, if we have one defined + this.inherited(arguments); + var store = this.store, fetchArgs = {}; + dojo.forEach(["query", "queryOptions", "onFetch"], function(i){ + if(this[i]){ + fetchArgs[i] = this[i]; + } + delete this[i]; + }, this); + if(store && store.getFeatures()["dojo.data.api.Identity"]){ + // Temporarily set our store to null so that it will get set + // and connected appropriately + this.store = null; + this.setStore(store, this._oValue, fetchArgs); + } + }, + + destroy: function(){ + // summary: + // Clean up our connections + dojo.forEach(this._notifyConnections || [], dojo.disconnect); + this.inherited(arguments); + }, + + _addOptionItem: function(/* dijit.form.__SelectOption */ option){ + // summary: + // User-overridable function which, for the given option, adds an + // item to the select. If the option doesn't have a value, then a + // separator is added in that place. Make sure to store the option + // in the created option widget. + }, + + _removeOptionItem: function(/* dijit.form.__SelectOption */ option){ + // summary: + // User-overridable function which, for the given option, removes + // its item from the select. + }, + + _setDisplay: function(/*String or String[]*/ newDisplay){ + // summary: + // Overridable function which will set the display for the + // widget. newDisplay is either a string (in the case of + // single selects) or array of strings (in the case of multi-selects) + }, + + _getChildren: function(){ + // summary: + // Overridable function to return the children that this widget contains. + return []; + }, + + _getSelectedOptionsAttr: function(){ + // summary: + // hooks into this.attr to provide a mechanism for getting the + // option items for the current value of the widget. + return this.getOptions(this.get("value")); + }, + + _pseudoLoadChildren: function(/* item[] */ items){ + // summary: + // a function that will "fake" loading children, if needed, and + // if we have set to not load children until the widget opens. + // items: + // An array of items that will be loaded, when needed + }, + + onSetStore: function(){ + // summary: + // a function that can be connected to in order to receive a + // notification that the store has finished loading and all options + // from that store are available + } +}); + +} + +if(!dojo._hasResource["dijit._KeyNavContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit._KeyNavContainer"] = true; +dojo.provide("dijit._KeyNavContainer"); + + +dojo.declare("dijit._KeyNavContainer", + dijit._Container, + { + + // summary: + // A _Container with keyboard navigation of its children. + // description: + // To use this mixin, call connectKeyNavHandlers() in + // postCreate() and call startupKeyNavChildren() in startup(). + // It provides normalized keyboard and focusing code for Container + // widgets. +/*===== + // focusedChild: [protected] Widget + // The currently focused child widget, or null if there isn't one + focusedChild: null, +=====*/ + + // tabIndex: Integer + // Tab index of the container; same as HTML tabIndex attribute. + // Note then when user tabs into the container, focus is immediately + // moved to the first item in the container. + tabIndex: "0", + + _keyNavCodes: {}, + + connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){ + // summary: + // Call in postCreate() to attach the keyboard handlers + // to the container. + // preKeyCodes: dojo.keys[] + // Key codes for navigating to the previous child. + // nextKeyCodes: dojo.keys[] + // Key codes for navigating to the next child. + // tags: + // protected + + var keyCodes = (this._keyNavCodes = {}); + var prev = dojo.hitch(this, this.focusPrev); + var next = dojo.hitch(this, this.focusNext); + dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; }); + dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; }); + this.connect(this.domNode, "onkeypress", "_onContainerKeypress"); + this.connect(this.domNode, "onfocus", "_onContainerFocus"); + }, + + startupKeyNavChildren: function(){ + // summary: + // Call in startup() to set child tabindexes to -1 + // tags: + // protected + dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild")); + }, + + addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){ + // summary: + // Add a child to our _Container + dijit._KeyNavContainer.superclass.addChild.apply(this, arguments); + this._startupChild(widget); + }, + + focus: function(){ + // summary: + // Default focus() implementation: focus the first child. + this.focusFirstChild(); + }, + + focusFirstChild: function(){ + // summary: + // Focus the first focusable child in the container. + // tags: + // protected + var child = this._getFirstFocusableChild(); + if(child){ // edge case: Menu could be empty or hidden + this.focusChild(child); + } + }, + + focusNext: function(){ + // summary: + // Focus the next widget + // tags: + // protected + var child = this._getNextFocusableChild(this.focusedChild, 1); + this.focusChild(child); + }, + + focusPrev: function(){ + // summary: + // Focus the last focusable node in the previous widget + // (ex: go to the ComboButton icon section rather than button section) + // tags: + // protected + var child = this._getNextFocusableChild(this.focusedChild, -1); + this.focusChild(child, true); + }, + + focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){ + // summary: + // Focus widget. + // widget: + // Reference to container's child widget + // last: + // If true and if widget has multiple focusable nodes, focus the + // last one instead of the first one + // tags: + // protected + + if(this.focusedChild && widget !== this.focusedChild){ + this._onChildBlur(this.focusedChild); + } + widget.focus(last ? "end" : "start"); + this.focusedChild = widget; + }, + + _startupChild: function(/*dijit._Widget*/ widget){ + // summary: + // Setup for each child widget + // description: + // Sets tabIndex=-1 on each child, so that the tab key will + // leave the container rather than visiting each child. + // tags: + // private + + widget.set("tabIndex", "-1"); + + this.connect(widget, "_onFocus", function(){ + // Set valid tabIndex so tabbing away from widget goes to right place, see #10272 + widget.set("tabIndex", this.tabIndex); + }); + this.connect(widget, "_onBlur", function(){ + widget.set("tabIndex", "-1"); + }); + }, + + _onContainerFocus: function(evt){ + // summary: + // Handler for when the container gets focus + // description: + // Initially the container itself has a tabIndex, but when it gets + // focus, switch focus to first child... + // tags: + // private + + // Note that we can't use _onFocus() because switching focus from the + // _onFocus() handler confuses the focus.js code + // (because it causes _onFocusNode() to be called recursively) + + // focus bubbles on Firefox, + // so just make sure that focus has really gone to the container + if(evt.target !== this.domNode){ return; } + + this.focusFirstChild(); + + // and then set the container's tabIndex to -1, + // (don't remove as that breaks Safari 4) + // so that tab or shift-tab will go to the fields after/before + // the container, rather than the container itself + dojo.attr(this.domNode, "tabIndex", "-1"); + }, + + _onBlur: function(evt){ + // When focus is moved away the container, and its descendant (popup) widgets, + // then restore the container's tabIndex so that user can tab to it again. + // Note that using _onBlur() so that this doesn't happen when focus is shifted + // to one of my child widgets (typically a popup) + if(this.tabIndex){ + dojo.attr(this.domNode, "tabIndex", this.tabIndex); + } + this.inherited(arguments); + }, + + _onContainerKeypress: function(evt){ + // summary: + // When a key is pressed, if it's an arrow key etc. then + // it's handled here. + // tags: + // private + if(evt.ctrlKey || evt.altKey){ return; } + var func = this._keyNavCodes[evt.charOrCode]; + if(func){ + func(); + dojo.stopEvent(evt); + } + }, + + _onChildBlur: function(/*dijit._Widget*/ widget){ + // summary: + // Called when focus leaves a child widget to go + // to a sibling widget. + // tags: + // protected + }, + + _getFirstFocusableChild: function(){ + // summary: + // Returns first child that can be focused + return this._getNextFocusableChild(null, 1); // dijit._Widget + }, + + _getNextFocusableChild: function(child, dir){ + // summary: + // Returns the next or previous focusable child, compared + // to "child" + // child: Widget + // The current widget + // dir: Integer + // * 1 = after + // * -1 = before + if(child){ + child = this._getSiblingOfChild(child, dir); + } + var children = this.getChildren(); + for(var i=0; i < children.length; i++){ + if(!child){ + child = children[(dir>0) ? 0 : (children.length-1)]; + } + if(child.isFocusable()){ + return child; // dijit._Widget + } + child = this._getSiblingOfChild(child, dir); + } + // no focusable child found + return null; // dijit._Widget + } + } +); + +} + +if(!dojo._hasResource["dijit.MenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.MenuItem"] = true; +dojo.provide("dijit.MenuItem"); + + + + + + +dojo.declare("dijit.MenuItem", + [dijit._Widget, dijit._Templated, dijit._Contained, dijit._CssStateMixin], + { + // summary: + // A line item in a Menu Widget + + // Make 3 columns + // icon, label, and expand arrow (BiDi-dependent) indicating sub-menu + templateString: dojo.cache("dijit", "templates/MenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitem\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">\n\t\t<div dojoAttachPoint=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\"/>\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"), + + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + label: { node: "containerNode", type: "innerHTML" }, + iconClass: { node: "iconNode", type: "class" } + }), + + baseClass: "dijitMenuItem", + + // label: String + // Menu text + label: '', + + // iconClass: String + // Class to apply to DOMNode to make it display an icon. + iconClass: "", + + // accelKey: String + // Text for the accelerator (shortcut) key combination. + // Note that although Menu can display accelerator keys there + // is no infrastructure to actually catch and execute these + // accelerators. + accelKey: "", + + // disabled: Boolean + // If true, the menu item is disabled. + // If false, the menu item is enabled. + disabled: false, + + _fillContent: function(/*DomNode*/ source){ + // If button label is specified as srcNodeRef.innerHTML rather than + // this.params.label, handle it here. + if(source && !("label" in this.params)){ + this.set('label', source.innerHTML); + } + }, + + postCreate: function(){ + this.inherited(arguments); + dojo.setSelectable(this.domNode, false); + var label = this.id+"_text"; + dojo.attr(this.containerNode, "id", label); + if(this.accelKeyNode){ + dojo.attr(this.accelKeyNode, "id", this.id + "_accel"); + label += " " + this.id + "_accel"; + } + dijit.setWaiState(this.domNode, "labelledby", label); + }, + + _onHover: function(){ + // summary: + // Handler when mouse is moved onto menu item + // tags: + // protected + this.getParent().onItemHover(this); + }, + + _onUnhover: function(){ + // summary: + // Handler when mouse is moved off of menu item, + // possibly to a child menu, or maybe to a sibling + // menuitem or somewhere else entirely. + // tags: + // protected + + // if we are unhovering the currently selected item + // then unselect it + this.getParent().onItemUnhover(this); + + // _onUnhover() is called when the menu is hidden (collapsed), due to clicking + // a MenuItem and having it execut. When that happens, FF and IE don't generate + // an onmouseout event for the MenuItem, so give _CssStateMixin some help + this._hovering = false; + this._setStateClass(); + }, + + _onClick: function(evt){ + // summary: + // Internal handler for click events on MenuItem. + // tags: + // private + this.getParent().onItemClick(this, evt); + dojo.stopEvent(evt); + }, + + onClick: function(/*Event*/ evt){ + // summary: + // User defined function to handle clicks + // tags: + // callback + }, + + focus: function(){ + // summary: + // Focus on this MenuItem + try{ + if(dojo.isIE == 8){ + // needed for IE8 which won't scroll TR tags into view on focus yet calling scrollIntoView creates flicker (#10275) + this.containerNode.focus(); + } + dijit.focus(this.focusNode); + }catch(e){ + // this throws on IE (at least) in some scenarios + } + }, + + _onFocus: function(){ + // summary: + // This is called by the focus manager when focus + // goes to this MenuItem or a child menu. + // tags: + // protected + this._setSelected(true); + this.getParent()._onItemFocus(this); + + this.inherited(arguments); + }, + + _setSelected: function(selected){ + // summary: + // Indicate that this node is the currently selected one + // tags: + // private + + /*** + * TODO: remove this method and calls to it, when _onBlur() is working for MenuItem. + * Currently _onBlur() gets called when focus is moved from the MenuItem to a child menu. + * That's not supposed to happen, but the problem is: + * In order to allow dijit.popup's getTopPopup() to work,a sub menu's popupParent + * points to the parent Menu, bypassing the parent MenuItem... thus the + * MenuItem is not in the chain of active widgets and gets a premature call to + * _onBlur() + */ + + dojo.toggleClass(this.domNode, "dijitMenuItemSelected", selected); + }, + + setLabel: function(/*String*/ content){ + // summary: + // Deprecated. Use set('label', ...) instead. + // tags: + // deprecated + dojo.deprecated("dijit.MenuItem.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0"); + this.set("label", content); + }, + + setDisabled: function(/*Boolean*/ disabled){ + // summary: + // Deprecated. Use set('disabled', bool) instead. + // tags: + // deprecated + dojo.deprecated("dijit.Menu.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0"); + this.set('disabled', disabled); + }, + _setDisabledAttr: function(/*Boolean*/ value){ + // summary: + // Hook for attr('disabled', ...) to work. + // Enable or disable this menu item. + this.disabled = value; + dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false'); + }, + _setAccelKeyAttr: function(/*String*/ value){ + // summary: + // Hook for attr('accelKey', ...) to work. + // Set accelKey on this menu item. + this.accelKey=value; + + this.accelKeyNode.style.display=value?"":"none"; + this.accelKeyNode.innerHTML=value; + //have to use colSpan to make it work in IE + dojo.attr(this.containerNode,'colSpan',value?"1":"2"); + } + }); + +} + +if(!dojo._hasResource["dijit.PopupMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.PopupMenuItem"] = true; +dojo.provide("dijit.PopupMenuItem"); + + + +dojo.declare("dijit.PopupMenuItem", + dijit.MenuItem, + { + _fillContent: function(){ + // summary: + // When Menu is declared in markup, this code gets the menu label and + // the popup widget from the srcNodeRef. + // description: + // srcNodeRefinnerHTML contains both the menu item text and a popup widget + // The first part holds the menu item text and the second part is the popup + // example: + // | <div dojoType="dijit.PopupMenuItem"> + // | <span>pick me</span> + // | <popup> ... </popup> + // | </div> + // tags: + // protected + + if(this.srcNodeRef){ + var nodes = dojo.query("*", this.srcNodeRef); + dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]); + + // save pointer to srcNode so we can grab the drop down widget after it's instantiated + this.dropDownContainer = this.srcNodeRef; + } + }, + + startup: function(){ + if(this._started){ return; } + this.inherited(arguments); + + // we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's + // land now. move it to dojo.doc.body. + if(!this.popup){ + var node = dojo.query("[widgetId]", this.dropDownContainer)[0]; + this.popup = dijit.byNode(node); + } + dojo.body().appendChild(this.popup.domNode); + this.popup.startup(); + + this.popup.domNode.style.display="none"; + if(this.arrowWrapper){ + dojo.style(this.arrowWrapper, "visibility", ""); + } + dijit.setWaiState(this.focusNode, "haspopup", "true"); + }, + + destroyDescendants: function(){ + if(this.popup){ + // Destroy the popup, unless it's already been destroyed. This can happen because + // the popup is a direct child of <body> even though it's logically my child. + if(!this.popup._destroyed){ + this.popup.destroyRecursive(); + } + delete this.popup; + } + this.inherited(arguments); + } + }); + + +} + +if(!dojo._hasResource["dijit.CheckedMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.CheckedMenuItem"] = true; +dojo.provide("dijit.CheckedMenuItem"); + + + +dojo.declare("dijit.CheckedMenuItem", + dijit.MenuItem, + { + // summary: + // A checkbox-like menu item for toggling on and off + + templateString: dojo.cache("dijit", "templates/CheckedMenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitemcheckbox\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">✓</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\"> </td>\n</tr>\n"), + + // checked: Boolean + // Our checked state + checked: false, + _setCheckedAttr: function(/*Boolean*/ checked){ + // summary: + // Hook so attr('checked', bool) works. + // Sets the class and state for the check box. + dojo.toggleClass(this.domNode, "dijitCheckedMenuItemChecked", checked); + dijit.setWaiState(this.domNode, "checked", checked); + this.checked = checked; + }, + + onChange: function(/*Boolean*/ checked){ + // summary: + // User defined function to handle check/uncheck events + // tags: + // callback + }, + + _onClick: function(/*Event*/ e){ + // summary: + // Clicking this item just toggles its state + // tags: + // private + if(!this.disabled){ + this.set("checked", !this.checked); + this.onChange(this.checked); + } + this.inherited(arguments); + } + }); + +} + +if(!dojo._hasResource["dijit.MenuSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.MenuSeparator"] = true; +dojo.provide("dijit.MenuSeparator"); + + + + + +dojo.declare("dijit.MenuSeparator", + [dijit._Widget, dijit._Templated, dijit._Contained], + { + // summary: + // A line between two menu items + + templateString: dojo.cache("dijit", "templates/MenuSeparator.html", "<tr class=\"dijitMenuSeparator\">\n\t<td class=\"dijitMenuSeparatorIconCell\">\n\t\t<div class=\"dijitMenuSeparatorTop\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n\t<td colspan=\"3\" class=\"dijitMenuSeparatorLabelCell\">\n\t\t<div class=\"dijitMenuSeparatorTop dijitMenuSeparatorLabel\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n</tr>\n"), + + postCreate: function(){ + dojo.setSelectable(this.domNode, false); + }, + + isFocusable: function(){ + // summary: + // Override to always return false + // tags: + // protected + + return false; // Boolean + } + }); + + +} + +if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.Menu"] = true; +dojo.provide("dijit.Menu"); + + + + + + + +dojo.declare("dijit._MenuBase", + [dijit._Widget, dijit._Templated, dijit._KeyNavContainer], +{ + // summary: + // Base class for Menu and MenuBar + + // parentMenu: [readonly] Widget + // pointer to menu that displayed me + parentMenu: null, + + // popupDelay: Integer + // number of milliseconds before hovering (without clicking) causes the popup to automatically open. + popupDelay: 500, + + startup: function(){ + if(this._started){ return; } + + dojo.forEach(this.getChildren(), function(child){ child.startup(); }); + this.startupKeyNavChildren(); + + this.inherited(arguments); + }, + + onExecute: function(){ + // summary: + // Attach point for notification about when a menu item has been executed. + // This is an internal mechanism used for Menus to signal to their parent to + // close them, because they are about to execute the onClick handler. In + // general developers should not attach to or override this method. + // tags: + // protected + }, + + onCancel: function(/*Boolean*/ closeAll){ + // summary: + // Attach point for notification about when the user cancels the current menu + // This is an internal mechanism used for Menus to signal to their parent to + // close them. In general developers should not attach to or override this method. + // tags: + // protected + }, + + _moveToPopup: function(/*Event*/ evt){ + // summary: + // This handles the right arrow key (left arrow key on RTL systems), + // which will either open a submenu, or move to the next item in the + // ancestor MenuBar + // tags: + // private + + if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){ + this.focusedChild._onClick(evt); + }else{ + var topMenu = this._getTopMenu(); + if(topMenu && topMenu._isMenuBar){ + topMenu.focusNext(); + } + } + }, + + _onPopupHover: function(/*Event*/ evt){ + // summary: + // This handler is called when the mouse moves over the popup. + // tags: + // private + + // if the mouse hovers over a menu popup that is in pending-close state, + // then stop the close operation. + // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker) + if(this.currentPopup && this.currentPopup._pendingClose_timer){ + var parentMenu = this.currentPopup.parentMenu; + // highlight the parent menu item pointing to this popup + if(parentMenu.focusedChild){ + parentMenu.focusedChild._setSelected(false); + } + parentMenu.focusedChild = this.currentPopup.from_item; + parentMenu.focusedChild._setSelected(true); + // cancel the pending close + this._stopPendingCloseTimer(this.currentPopup); + } + }, + + onItemHover: function(/*MenuItem*/ item){ + // summary: + // Called when cursor is over a MenuItem. + // tags: + // protected + + // Don't do anything unless user has "activated" the menu by: + // 1) clicking it + // 2) opening it from a parent menu (which automatically focuses it) + if(this.isActive){ + this.focusChild(item); + if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){ + this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay); + } + } + // if the user is mixing mouse and keyboard navigation, + // then the menu may not be active but a menu item has focus, + // but it's not the item that the mouse just hovered over. + // To avoid both keyboard and mouse selections, use the latest. + if(this.focusedChild){ + this.focusChild(item); + } + this._hoveredChild = item; + }, + + _onChildBlur: function(item){ + // summary: + // Called when a child MenuItem becomes inactive because focus + // has been removed from the MenuItem *and* it's descendant menus. + // tags: + // private + this._stopPopupTimer(); + item._setSelected(false); + // Close all popups that are open and descendants of this menu + var itemPopup = item.popup; + if(itemPopup){ + this._stopPendingCloseTimer(itemPopup); + itemPopup._pendingClose_timer = setTimeout(function(){ + itemPopup._pendingClose_timer = null; + if(itemPopup.parentMenu){ + itemPopup.parentMenu.currentPopup = null; + } + dijit.popup.close(itemPopup); // this calls onClose + }, this.popupDelay); + } + }, + + onItemUnhover: function(/*MenuItem*/ item){ + // summary: + // Callback fires when mouse exits a MenuItem + // tags: + // protected + + if(this.isActive){ + this._stopPopupTimer(); + } + if(this._hoveredChild == item){ this._hoveredChild = null; } + }, + + _stopPopupTimer: function(){ + // summary: + // Cancels the popup timer because the user has stop hovering + // on the MenuItem, etc. + // tags: + // private + if(this.hover_timer){ + clearTimeout(this.hover_timer); + this.hover_timer = null; + } + }, + + _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){ + // summary: + // Cancels the pending-close timer because the close has been preempted + // tags: + // private + if(popup._pendingClose_timer){ + clearTimeout(popup._pendingClose_timer); + popup._pendingClose_timer = null; + } + }, + + _stopFocusTimer: function(){ + // summary: + // Cancels the pending-focus timer because the menu was closed before focus occured + // tags: + // private + if(this._focus_timer){ + clearTimeout(this._focus_timer); + this._focus_timer = null; + } + }, + + _getTopMenu: function(){ + // summary: + // Returns the top menu in this chain of Menus + // tags: + // private + for(var top=this; top.parentMenu; top=top.parentMenu); + return top; + }, + + onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ + // summary: + // Handle clicks on an item. + // tags: + // private + + // this can't be done in _onFocus since the _onFocus events occurs asynchronously + if(typeof this.isShowingNow == 'undefined'){ // non-popup menu + this._markActive(); + } + + this.focusChild(item); + + if(item.disabled){ return false; } + + if(item.popup){ + this._openPopup(); + }else{ + // before calling user defined handler, close hierarchy of menus + // and restore focus to place it was when menu was opened + this.onExecute(); + + // user defined handler for click + item.onClick(evt); + } + }, + + _openPopup: function(){ + // summary: + // Open the popup to the side of/underneath the current menu item + // tags: + // protected + + this._stopPopupTimer(); + var from_item = this.focusedChild; + if(!from_item){ return; } // the focused child lost focus since the timer was started + var popup = from_item.popup; + if(popup.isShowingNow){ return; } + if(this.currentPopup){ + this._stopPendingCloseTimer(this.currentPopup); + dijit.popup.close(this.currentPopup); + } + popup.parentMenu = this; + popup.from_item = from_item; // helps finding the parent item that should be focused for this popup + var self = this; + dijit.popup.open({ + parent: this, + popup: popup, + around: from_item.domNode, + orient: this._orient || (this.isLeftToRight() ? + {'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} : + {'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}), + onCancel: function(){ // called when the child menu is canceled + // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus + // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro) + self.focusChild(from_item); // put focus back on my node + self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved) + from_item._setSelected(true); // oops, _cleanUp() deselected the item + self.focusedChild = from_item; // and unset focusedChild + }, + onExecute: dojo.hitch(this, "_cleanUp") + }); + + this.currentPopup = popup; + // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items + popup.connect(popup.domNode, "onmouseenter", dojo.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close + + if(popup.focus){ + // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar), + // if the cursor happens to collide with the popup, it will generate an onmouseover event + // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that + // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742) + popup._focus_timer = setTimeout(dojo.hitch(popup, function(){ + this._focus_timer = null; + this.focus(); + }), 0); + } + }, + + _markActive: function(){ + // summary: + // Mark this menu's state as active. + // Called when this Menu gets focus from: + // 1) clicking it (mouse or via space/arrow key) + // 2) being opened by a parent menu. + // This is not called just from mouse hover. + // Focusing a menu via TAB does NOT automatically set isActive + // since TAB is a navigation operation and not a selection one. + // For Windows apps, pressing the ALT key focuses the menubar + // menus (similar to TAB navigation) but the menu is not active + // (ie no dropdown) until an item is clicked. + this.isActive = true; + dojo.addClass(this.domNode, "dijitMenuActive"); + dojo.removeClass(this.domNode, "dijitMenuPassive"); + }, + + onOpen: function(/*Event*/ e){ + // summary: + // Callback when this menu is opened. + // This is called by the popup manager as notification that the menu + // was opened. + // tags: + // private + + this.isShowingNow = true; + this._markActive(); + }, + + _markInactive: function(){ + // summary: + // Mark this menu's state as inactive. + this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here + dojo.removeClass(this.domNode, "dijitMenuActive"); + dojo.addClass(this.domNode, "dijitMenuPassive"); + }, + + onClose: function(){ + // summary: + // Callback when this menu is closed. + // This is called by the popup manager as notification that the menu + // was closed. + // tags: + // private + + this._stopFocusTimer(); + this._markInactive(); + this.isShowingNow = false; + this.parentMenu = null; + }, + + _closeChild: function(){ + // summary: + // Called when submenu is clicked or focus is lost. Close hierarchy of menus. + // tags: + // private + this._stopPopupTimer(); + if(this.focusedChild){ // unhighlight the focused item + this.focusedChild._setSelected(false); + this.focusedChild._onUnhover(); + this.focusedChild = null; + } + if(this.currentPopup){ + // Close all popups that are open and descendants of this menu + dijit.popup.close(this.currentPopup); + this.currentPopup = null; + } + }, + + _onItemFocus: function(/*MenuItem*/ item){ + // summary: + // Called when child of this Menu gets focus from: + // 1) clicking it + // 2) tabbing into it + // 3) being opened by a parent menu. + // This is not called just from mouse hover. + if(this._hoveredChild && this._hoveredChild != item){ + this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection + } + }, + + _onBlur: function(){ + // summary: + // Called when focus is moved away from this Menu and it's submenus. + // tags: + // protected + this._cleanUp(); + this.inherited(arguments); + }, + + _cleanUp: function(){ + // summary: + // Called when the user is done with this menu. Closes hierarchy of menus. + // tags: + // private + + this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close + if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose + this._markInactive(); + } + } +}); + +dojo.declare("dijit.Menu", + dijit._MenuBase, + { + // summary + // A context menu you can assign to multiple elements + + // TODO: most of the code in here is just for context menu (right-click menu) + // support. In retrospect that should have been a separate class (dijit.ContextMenu). + // Split them for 2.0 + + constructor: function(){ + this._bindings = []; + }, + + templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" waiRole=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=0>\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"), + + baseClass: "dijitMenu", + + // targetNodeIds: [const] String[] + // Array of dom node ids of nodes to attach to. + // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes. + targetNodeIds: [], + + // contextMenuForWindow: [const] Boolean + // If true, right clicking anywhere on the window will cause this context menu to open. + // If false, must specify targetNodeIds. + contextMenuForWindow: false, + + // leftClickToOpen: [const] Boolean + // If true, menu will open on left click instead of right click, similiar to a file menu. + leftClickToOpen: false, + + // refocus: Boolean + // When this menu closes, re-focus the element which had focus before it was opened. + refocus: true, + + postCreate: function(){ + if(this.contextMenuForWindow){ + this.bindDomNode(dojo.body()); + }else{ + // TODO: should have _setTargetNodeIds() method to handle initialization and a possible + // later attr('targetNodeIds', ...) call. There's also a problem that targetNodeIds[] + // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610) + dojo.forEach(this.targetNodeIds, this.bindDomNode, this); + } + var k = dojo.keys, l = this.isLeftToRight(); + this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW; + this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW; + this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]); + }, + + _onKeyPress: function(/*Event*/ evt){ + // summary: + // Handle keyboard based menu navigation. + // tags: + // protected + + if(evt.ctrlKey || evt.altKey){ return; } + + switch(evt.charOrCode){ + case this._openSubMenuKey: + this._moveToPopup(evt); + dojo.stopEvent(evt); + break; + case this._closeSubMenuKey: + if(this.parentMenu){ + if(this.parentMenu._isMenuBar){ + this.parentMenu.focusPrev(); + }else{ + this.onCancel(false); + } + }else{ + dojo.stopEvent(evt); + } + break; + } + }, + + // thanks burstlib! + _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){ + // summary: + // Returns the window reference of the passed iframe + // tags: + // private + var win = dojo.window.get(this._iframeContentDocument(iframe_el)) || + // Moz. TODO: is this available when defaultView isn't? + this._iframeContentDocument(iframe_el)['__parent__'] || + (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null; + return win; // Window + }, + + _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){ + // summary: + // Returns a reference to the document object inside iframe_el + // tags: + // protected + var doc = iframe_el.contentDocument // W3 + || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE + || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document) + || null; + return doc; // HTMLDocument + }, + + bindDomNode: function(/*String|DomNode*/ node){ + // summary: + // Attach menu to given node + node = dojo.byId(node); + + var cn; // Connect node + + // Support context menus on iframes. Rather than binding to the iframe itself we need + // to bind to the <body> node inside the iframe. + if(node.tagName.toLowerCase() == "iframe"){ + var iframe = node, + win = this._iframeContentWindow(iframe); + cn = dojo.withGlobal(win, dojo.body); + }else{ + + // To capture these events at the top level, attach to <html>, not <body>. + // Otherwise right-click context menu just doesn't work. + cn = (node == dojo.body() ? dojo.doc.documentElement : node); + } + + + // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode()) + var binding = { + node: node, + iframe: iframe + }; + + // Save info about binding in _bindings[], and make node itself record index(+1) into + // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may + // start with a number, which fails on FF/safari. + dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding)); + + // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished + // loading yet, in which case we need to wait for the onload event first, and then connect + // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so + // we need to monitor keyboard events in addition to the oncontextmenu event. + var doConnects = dojo.hitch(this, function(cn){ + return [ + // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu, + // rather than shift-F10? + dojo.connect(cn, this.leftClickToOpen ? "onclick" : "oncontextmenu", this, function(evt){ + // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler + dojo.stopEvent(evt); + this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY}); + }), + dojo.connect(cn, "onkeydown", this, function(evt){ + if(evt.shiftKey && evt.keyCode == dojo.keys.F10){ + dojo.stopEvent(evt); + this._scheduleOpen(evt.target, iframe); // no coords - open near target node + } + }) + ]; + }); + binding.connects = cn ? doConnects(cn) : []; + + if(iframe){ + // Setup handler to [re]bind to the iframe when the contents are initially loaded, + // and every time the contents change. + // Need to do this b/c we are actually binding to the iframe's <body> node. + // Note: can't use dojo.connect(), see #9609. + + binding.onloadHandler = dojo.hitch(this, function(){ + // want to remove old connections, but IE throws exceptions when trying to + // access the <body> node because it's already gone, or at least in a state of limbo + + var win = this._iframeContentWindow(iframe); + cn = dojo.withGlobal(win, dojo.body); + binding.connects = doConnects(cn); + }); + if(iframe.addEventListener){ + iframe.addEventListener("load", binding.onloadHandler, false); + }else{ + iframe.attachEvent("onload", binding.onloadHandler); + } + } + }, + + unBindDomNode: function(/*String|DomNode*/ nodeName){ + // summary: + // Detach menu from given node + + var node; + try{ + node = dojo.byId(nodeName); + }catch(e){ + // On IE the dojo.byId() call will get an exception if the attach point was + // the <body> node of an <iframe> that has since been reloaded (and thus the + // <body> node is in a limbo state of destruction. + return; + } + + // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array + var attrName = "_dijitMenu" + this.id; + if(node && dojo.hasAttr(node, attrName)){ + var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid]; + dojo.forEach(b.connects, dojo.disconnect); + + // Remove listener for iframe onload events + var iframe = b.iframe; + if(iframe){ + if(iframe.removeEventListener){ + iframe.removeEventListener("load", b.onloadHandler, false); + }else{ + iframe.detachEvent("onload", b.onloadHandler); + } + } + + dojo.removeAttr(node, attrName); + delete this._bindings[bid]; + } + }, + + _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){ + // summary: + // Set timer to display myself. Using a timer rather than displaying immediately solves + // two problems: + // + // 1. IE: without the delay, focus work in "open" causes the system + // context menu to appear in spite of stopEvent. + // + // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event + // even after a dojo.stopEvent(e). (Shift-F10 on windows doesn't generate the + // oncontextmenu event.) + + if(!this._openTimer){ + this._openTimer = setTimeout(dojo.hitch(this, function(){ + delete this._openTimer; + this._openMyself({ + target: target, + iframe: iframe, + coords: coords + }); + }), 1); + } + }, + + _openMyself: function(args){ + // summary: + // Internal function for opening myself when the user does a right-click or something similar. + // args: + // This is an Object containing: + // * target: + // The node that is being clicked + // * iframe: + // If an <iframe> is being clicked, iframe points to that iframe + // * coords: + // Put menu at specified x/y position in viewport, or if iframe is + // specified, then relative to iframe. + // + // _openMyself() formerly took the event object, and since various code references + // evt.target (after connecting to _openMyself()), using an Object for parameters + // (so that old code still works). + + var target = args.target, + iframe = args.iframe, + coords = args.coords; + + // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard) + // then near the node the menu is assigned to. + if(coords){ + if(iframe){ + // Specified coordinates are on <body> node of an <iframe>, convert to match main document + var od = target.ownerDocument, + ifc = dojo.position(iframe, true), + win = this._iframeContentWindow(iframe), + scroll = dojo.withGlobal(win, "_docScroll", dojo); + + var cs = dojo.getComputedStyle(iframe), + tp = dojo._toPixelValue, + left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0), + top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0); + + coords.x += ifc.x + left - scroll.x; + coords.y += ifc.y + top - scroll.y; + } + }else{ + coords = dojo.position(target, true); + coords.x += 10; + coords.y += 10; + } + + var self=this; + var savedFocus = dijit.getFocus(this); + function closeAndRestoreFocus(){ + // user has clicked on a menu or popup + if(self.refocus){ + dijit.focus(savedFocus); + } + dijit.popup.close(self); + } + dijit.popup.open({ + popup: this, + x: coords.x, + y: coords.y, + onExecute: closeAndRestoreFocus, + onCancel: closeAndRestoreFocus, + orient: this.isLeftToRight() ? 'L' : 'R' + }); + this.focus(); + + this._onBlur = function(){ + this.inherited('_onBlur', arguments); + // Usually the parent closes the child widget but if this is a context + // menu then there is no parent + dijit.popup.close(this); + // don't try to restore focus; user has clicked another part of the screen + // and set focus there + }; + }, + + uninitialize: function(){ + dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this); + this.inherited(arguments); + } +} +); + +// Back-compat (TODO: remove in 2.0) + + + + + + +} + +if(!dojo._hasResource["dijit.form.Select"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.Select"] = true; +dojo.provide("dijit.form.Select"); + + + + + + + + +dojo.declare("dijit.form._SelectMenu", dijit.Menu, { + // summary: + // An internally-used menu for dropdown that allows us a vertical scrollbar + buildRendering: function(){ + // summary: + // Stub in our own changes, so that our domNode is not a table + // otherwise, we won't respond correctly to heights/overflows + this.inherited(arguments); + var o = (this.menuTableNode = this.domNode); + var n = (this.domNode = dojo.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}})); + if(o.parentNode){ + o.parentNode.replaceChild(n, o); + } + dojo.removeClass(o, "dijitMenuTable"); + n.className = o.className + " dijitSelectMenu"; + o.className = "dijitReset dijitMenuTable"; + dijit.setWaiRole(o,"listbox"); + dijit.setWaiRole(n,"presentation"); + n.appendChild(o); + }, + resize: function(/*Object*/ mb){ + // summary: + // Overridden so that we are able to handle resizing our + // internal widget. Note that this is not a "full" resize + // implementation - it only works correctly if you pass it a + // marginBox. + // + // mb: Object + // The margin box to set this dropdown to. + if(mb){ + dojo.marginBox(this.domNode, mb); + if("w" in mb){ + // We've explicitly set the wrapper <div>'s width, so set <table> width to match. + // 100% is safer than a pixel value because there may be a scroll bar with + // browser/OS specific width. + this.menuTableNode.style.width = "100%"; + } + } + } +}); + +dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropDown], { + // summary: + // This is a "styleable" select box - it is basically a DropDownButton which + // can take a <select> as its input. + + baseClass: "dijitSelect", + + templateString: dojo.cache("dijit.form", "templates/Select.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdojoAttachPoint=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\twaiRole=\"combobox\" waiState=\"haspopup-true\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" waiRole=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" dojoAttachPoint=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} dojoAttachPoint=\"valueNode\" value=\"${value}\" waiState=\"hidden-true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"titleNode\" waiRole=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">▼</div\n\t\t></td\n\t></tr></tbody\n></table>\n"), + + // attributeMap: Object + // Add in our style to be applied to the focus node + attributeMap: dojo.mixin(dojo.clone(dijit.form._FormSelectWidget.prototype.attributeMap),{style:"tableNode"}), + + // required: Boolean + // Can be true or false, default is false. + required: false, + + // state: String + // Shows current state (ie, validation result) of input (Normal, Warning, or Error) + state: "", + + // tooltipPosition: String[] + // See description of dijit.Tooltip.defaultPosition for details on this parameter. + tooltipPosition: [], + + // emptyLabel: string + // What to display in an "empty" dropdown + emptyLabel: "", + + // _isLoaded: Boolean + // Whether or not we have been loaded + _isLoaded: false, + + // _childrenLoaded: Boolean + // Whether or not our children have been loaded + _childrenLoaded: false, + + _fillContent: function(){ + // summary: + // Set the value to be the first, or the selected index + this.inherited(arguments); + if(this.options.length && !this.value && this.srcNodeRef){ + var si = this.srcNodeRef.selectedIndex; + this.value = this.options[si != -1 ? si : 0].value; + } + + // Create the dropDown widget + this.dropDown = new dijit.form._SelectMenu({id: this.id + "_menu"}); + dojo.addClass(this.dropDown.domNode, this.baseClass + "Menu"); + }, + + _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){ + // summary: + // For the given option, return the menu item that should be + // used to display it. This can be overridden as needed + if(!option.value){ + // We are a separator (no label set for it) + return new dijit.MenuSeparator(); + }else{ + // Just a regular menu option + var click = dojo.hitch(this, "_setValueAttr", option); + var item = new dijit.MenuItem({ + option: option, + label: option.label, + onClick: click, + disabled: option.disabled || false + }); + dijit.setWaiRole(item.focusNode, "listitem"); + return item; + } + }, + + _addOptionItem: function(/*dijit.form.__SelectOption*/ option){ + // summary: + // For the given option, add an option to our dropdown. + // If the option doesn't have a value, then a separator is added + // in that place. + if(this.dropDown){ + this.dropDown.addChild(this._getMenuItemForOption(option)); + } + }, + + _getChildren: function(){ + if(!this.dropDown){ + return []; + } + return this.dropDown.getChildren(); + }, + + _loadChildren: function(/*Boolean*/ loadMenuItems){ + // summary: + // Resets the menu and the length attribute of the button - and + // ensures that the label is appropriately set. + // loadMenuItems: Boolean + // actually loads the child menu items - we only do this when we are + // populating for showing the dropdown. + + if(loadMenuItems === true){ + // this.inherited destroys this.dropDown's child widgets (MenuItems). + // Avoid this.dropDown (Menu widget) having a pointer to a destroyed widget (which will cause + // issues later in _setSelected). (see #10296) + if(this.dropDown){ + delete this.dropDown.focusedChild; + } + if(this.options.length){ + this.inherited(arguments); + }else{ + // Drop down menu is blank but add one blank entry just so something appears on the screen + // to let users know that they are no choices (mimicing native select behavior) + dojo.forEach(this._getChildren(), function(child){ child.destroyRecursive(); }); + var item = new dijit.MenuItem({label: " "}); + this.dropDown.addChild(item); + } + }else{ + this._updateSelection(); + } + + var len = this.options.length; + this._isLoaded = false; + this._childrenLoaded = true; + + if(!this._loadingStore){ + // Don't call this if we are loading - since we will handle it later + this._setValueAttr(this.value); + } + }, + + _setValueAttr: function(value){ + this.inherited(arguments); + dojo.attr(this.valueNode, "value", this.get("value")); + }, + + _setDisplay: function(/*String*/ newDisplay){ + // summary: + // sets the display for the given value (or values) + this.containerNode.innerHTML = '<span class="dijitReset dijitInline ' + this.baseClass + 'Label">' + + (newDisplay || this.emptyLabel || " ") + + '</span>'; + dijit.setWaiState(this.focusNode, "valuetext", (newDisplay || this.emptyLabel || " ") ); + }, + + validate: function(/*Boolean*/ isFocused){ + // summary: + // Called by oninit, onblur, and onkeypress. + // description: + // Show missing or invalid messages if appropriate, and highlight textbox field. + // Used when a select is initially set to no value and the user is required to + // set the value. + + var isValid = this.isValid(isFocused); + this.state = isValid ? "" : "Error"; + this._setStateClass(); + dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true"); + var message = isValid ? "" : this._missingMsg; + if(this._message !== message){ + this._message = message; + dijit.hideTooltip(this.domNode); + if(message){ + dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); + } + } + return isValid; + }, + + isValid: function(/*Boolean*/ isFocused){ + // summary: + // Whether or not this is a valid value. The only way a Select + // can be invalid is when it's required but nothing is selected. + return (!this.required || !(/^\s*$/.test(this.value))); + }, + + reset: function(){ + // summary: + // Overridden so that the state will be cleared. + this.inherited(arguments); + dijit.hideTooltip(this.domNode); + this.state = ""; + this._setStateClass(); + delete this._message; + }, + + postMixInProperties: function(){ + // summary: + // set the missing message + this.inherited(arguments); + this._missingMsg = dojo.i18n.getLocalization("dijit.form", "validate", + this.lang).missingMessage; + }, + + postCreate: function(){ + this.inherited(arguments); + if(this.tableNode.style.width){ + dojo.addClass(this.domNode, this.baseClass + "FixedWidth"); + } + }, + + isLoaded: function(){ + return this._isLoaded; + }, + + loadDropDown: function(/*Function*/ loadCallback){ + // summary: + // populates the menu + this._loadChildren(true); + this._isLoaded = true; + loadCallback(); + }, + + closeDropDown: function(){ + // overriding _HasDropDown.closeDropDown() + this.inherited(arguments); + + if(this.dropDown && this.dropDown.menuTableNode){ + // Erase possible width: 100% setting from _SelectMenu.resize(). + // Leaving it would interfere with the next openDropDown() call, which + // queries the natural size of the drop down. + this.dropDown.menuTableNode.style.width = ""; + } + }, + + uninitialize: function(preserveDom){ + if(this.dropDown && !this.dropDown._destroyed){ + this.dropDown.destroyRecursive(preserveDom); + delete this.dropDown; + } + this.inherited(arguments); + } +}); + +} + +if(!dojo._hasResource["dijit.form.SimpleTextarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.SimpleTextarea"] = true; +dojo.provide("dijit.form.SimpleTextarea"); + + + +dojo.declare("dijit.form.SimpleTextarea", + dijit.form.TextBox, + { + // summary: + // A simple textarea that degrades, and responds to + // minimal LayoutContainer usage, and works with dijit.form.Form. + // Doesn't automatically size according to input, like Textarea. + // + // example: + // | <textarea dojoType="dijit.form.SimpleTextarea" name="foo" value="bar" rows=30 cols=40></textarea> + // + // example: + // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo"); + + baseClass: "dijitTextBox dijitTextArea", + + attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { + rows:"textbox", cols: "textbox" + }), + + // rows: Number + // The number of rows of text. + rows: "3", + + // rows: Number + // The number of characters per line. + cols: "20", + + templateString: "<textarea ${!nameAttrSetting} dojoAttachPoint='focusNode,containerNode,textbox' autocomplete='off'></textarea>", + + postMixInProperties: function(){ + // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef) + if(!this.value && this.srcNodeRef){ + this.value = this.srcNodeRef.value; + } + this.inherited(arguments); + }, + + filter: function(/*String*/ value){ + // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines + // as \r\n instead of just \n + if(value){ + value = value.replace(/\r/g,""); + } + return this.inherited(arguments); + }, + + postCreate: function(){ + this.inherited(arguments); + if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6 + dojo.addClass(this.textbox, "dijitTextAreaCols"); + } + }, + + _previousValue: "", + _onInput: function(/*Event?*/ e){ + // Override TextBox._onInput() to enforce maxLength restriction + if(this.maxLength){ + var maxLength = parseInt(this.maxLength); + var value = this.textbox.value.replace(/\r/g,''); + var overflow = value.length - maxLength; + if(overflow > 0){ + if(e){ dojo.stopEvent(e); } + var textarea = this.textbox; + if(textarea.selectionStart){ + var pos = textarea.selectionStart; + var cr = 0; + if(dojo.isOpera){ + cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length; + } + this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr); + textarea.setSelectionRange(pos-overflow, pos-overflow); + }else if(dojo.doc.selection){ //IE + textarea.focus(); + var range = dojo.doc.selection.createRange(); + // delete overflow characters + range.moveStart("character", -overflow); + range.text = ''; + // show cursor + range.select(); + } + } + this._previousValue = this.textbox.value; + } + this.inherited(arguments); + } +}); + +} + +if(!dojo._hasResource["dijit.InlineEditBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.InlineEditBox"] = true; +dojo.provide("dijit.InlineEditBox"); + + + + + + + + + + +dojo.declare("dijit.InlineEditBox", + dijit._Widget, + { + // summary: + // An element with in-line edit capabilites + // + // description: + // Behavior for an existing node (`<p>`, `<div>`, `<span>`, etc.) so that + // when you click it, an editor shows up in place of the original + // text. Optionally, Save and Cancel button are displayed below the edit widget. + // When Save is clicked, the text is pulled from the edit + // widget and redisplayed and the edit widget is again hidden. + // By default a plain Textarea widget is used as the editor (or for + // inline values a TextBox), but you can specify an editor such as + // dijit.Editor (for editing HTML) or a Slider (for adjusting a number). + // An edit widget must support the following API to be used: + // - displayedValue or value as initialization parameter, + // and available through set('displayedValue') / set('value') + // - void focus() + // - DOM-node focusNode = node containing editable text + + // editing: [readonly] Boolean + // Is the node currently in edit mode? + editing: false, + + // autoSave: Boolean + // Changing the value automatically saves it; don't have to push save button + // (and save button isn't even displayed) + autoSave: true, + + // buttonSave: String + // Save button label + buttonSave: "", + + // buttonCancel: String + // Cancel button label + buttonCancel: "", + + // renderAsHtml: Boolean + // Set this to true if the specified Editor's value should be interpreted as HTML + // rather than plain text (ex: `dijit.Editor`) + renderAsHtml: false, + + // editor: String + // Class name for Editor widget + editor: "dijit.form.TextBox", + + // editorWrapper: String + // Class name for widget that wraps the editor widget, displaying save/cancel + // buttons. + editorWrapper: "dijit._InlineEditor", + + // editorParams: Object + // Set of parameters for editor, like {required: true} + editorParams: {}, + + onChange: function(value){ + // summary: + // Set this handler to be notified of changes to value. + // tags: + // callback + }, + + onCancel: function(){ + // summary: + // Set this handler to be notified when editing is cancelled. + // tags: + // callback + }, + + // width: String + // Width of editor. By default it's width=100% (ie, block mode). + width: "100%", + + // value: String + // The display value of the widget in read-only mode + value: "", + + // noValueIndicator: [const] String + // The text that gets displayed when there is no value (so that the user has a place to click to edit) + noValueIndicator: dojo.isIE <= 6 ? // font-family needed on IE6 but it messes up IE8 + "<span style='font-family: wingdings; text-decoration: underline;'>    ✍    </span>" : + "<span style='text-decoration: underline;'>    ✍    </span>", + + constructor: function(){ + // summary: + // Sets up private arrays etc. + // tags: + // private + this.editorParams = {}; + }, + + postMixInProperties: function(){ + this.inherited(arguments); + + // save pointer to original source node, since Widget nulls-out srcNodeRef + this.displayNode = this.srcNodeRef; + + // connect handlers to the display node + var events = { + ondijitclick: "_onClick", + onmouseover: "_onMouseOver", + onmouseout: "_onMouseOut", + onfocus: "_onMouseOver", + onblur: "_onMouseOut" + }; + for(var name in events){ + this.connect(this.displayNode, name, events[name]); + } + dijit.setWaiRole(this.displayNode, "button"); + if(!this.displayNode.getAttribute("tabIndex")){ + this.displayNode.setAttribute("tabIndex", 0); + } + + if(!this.value && !("value" in this.params)){ // "" is a good value if specified directly so check params){ + this.value = dojo.trim(this.renderAsHtml ? this.displayNode.innerHTML : + (this.displayNode.innerText||this.displayNode.textContent||"")); + } + if(!this.value){ + this.displayNode.innerHTML = this.noValueIndicator; + } + + dojo.addClass(this.displayNode, 'dijitInlineEditBoxDisplayMode'); + }, + + setDisabled: function(/*Boolean*/ disabled){ + // summary: + // Deprecated. Use set('disabled', ...) instead. + // tags: + // deprecated + dojo.deprecated("dijit.InlineEditBox.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0"); + this.set('disabled', disabled); + }, + + _setDisabledAttr: function(/*Boolean*/ disabled){ + // summary: + // Hook to make set("disabled", ...) work. + // Set disabled state of widget. + this.disabled = disabled; + dijit.setWaiState(this.domNode, "disabled", disabled); + if(disabled){ + this.displayNode.removeAttribute("tabIndex"); + }else{ + this.displayNode.setAttribute("tabIndex", 0); + } + dojo.toggleClass(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled", disabled); + }, + + _onMouseOver: function(){ + // summary: + // Handler for onmouseover and onfocus event. + // tags: + // private + if(!this.disabled){ + dojo.addClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover"); + } + }, + + _onMouseOut: function(){ + // summary: + // Handler for onmouseout and onblur event. + // tags: + // private + dojo.removeClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover"); + }, + + _onClick: function(/*Event*/ e){ + // summary: + // Handler for onclick event. + // tags: + // private + if(this.disabled){ return; } + if(e){ dojo.stopEvent(e); } + this._onMouseOut(); + + // Since FF gets upset if you move a node while in an event handler for that node... + setTimeout(dojo.hitch(this, "edit"), 0); + }, + + edit: function(){ + // summary: + // Display the editor widget in place of the original (read only) markup. + // tags: + // private + + if(this.disabled || this.editing){ return; } + this.editing = true; + + // save some display node values that can be restored later + this._savedPosition = dojo.style(this.displayNode, "position") || "static"; + this._savedOpacity = dojo.style(this.displayNode, "opacity") || "1"; + this._savedTabIndex = dojo.attr(this.displayNode, "tabIndex") || "0"; + + if(this.wrapperWidget){ + var ew = this.wrapperWidget.editWidget; + ew.set("displayedValue" in ew ? "displayedValue" : "value", this.value); + }else{ + // Placeholder for edit widget + // Put place holder (and eventually editWidget) before the display node so that it's positioned correctly + // when Calendar dropdown appears, which happens automatically on focus. + var placeholder = dojo.create("span", null, this.domNode, "before"); + + // Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons) + var ewc = dojo.getObject(this.editorWrapper); + this.wrapperWidget = new ewc({ + value: this.value, + buttonSave: this.buttonSave, + buttonCancel: this.buttonCancel, + dir: this.dir, + lang: this.lang, + tabIndex: this._savedTabIndex, + editor: this.editor, + inlineEditBox: this, + sourceStyle: dojo.getComputedStyle(this.displayNode), + save: dojo.hitch(this, "save"), + cancel: dojo.hitch(this, "cancel") + }, placeholder); + } + var ww = this.wrapperWidget; + + if(dojo.isIE){ + dijit.focus(dijit.getFocus()); // IE (at least 8) needs help with tab order changes + } + // to avoid screen jitter, we first create the editor with position:absolute, visibility:hidden, + // and then when it's finished rendering, we switch from display mode to editor + // position:absolute releases screen space allocated to the display node + // opacity:0 is the same as visibility:hidden but is still focusable + // visiblity:hidden removes focus outline + + dojo.style(this.displayNode, { position: "absolute", opacity: "0", display: "none" }); // makes display node invisible, display style used for focus-ability + dojo.style(ww.domNode, { position: this._savedPosition, visibility: "visible", opacity: "1" }); + dojo.attr(this.displayNode, "tabIndex", "-1"); // needed by WebKit for TAB from editor to skip displayNode + + // Replace the display widget with edit widget, leaving them both displayed for a brief time so that + // focus can be shifted without incident. (browser may needs some time to render the editor.) + setTimeout(dojo.hitch(this, function(){ + ww.focus(); // both nodes are showing, so we can switch focus safely + ww._resetValue = ww.getValue(); + }), 0); + }, + + _onBlur: function(){ + // summary: + // Called when focus moves outside the InlineEditBox. + // Performs garbage collection. + // tags: + // private + + this.inherited(arguments); + if(!this.editing){ + /* causes IE focus problems, see TooltipDialog_a11y.html... + setTimeout(dojo.hitch(this, function(){ + if(this.wrapperWidget){ + this.wrapperWidget.destroy(); + delete this.wrapperWidget; + } + }), 0); + */ + } + }, + + destroy: function(){ + if(this.wrapperWidget){ + this.wrapperWidget.destroy(); + delete this.wrapperWidget; + } + this.inherited(arguments); + }, + + _showText: function(/*Boolean*/ focus){ + // summary: + // Revert to display mode, and optionally focus on display node + // tags: + // private + + var ww = this.wrapperWidget; + dojo.style(ww.domNode, { position: "absolute", visibility: "hidden", opacity: "0" }); // hide the editor from mouse/keyboard events + dojo.style(this.displayNode, { position: this._savedPosition, opacity: this._savedOpacity, display: "" }); // make the original text visible + dojo.attr(this.displayNode, "tabIndex", this._savedTabIndex); + if(focus){ + dijit.focus(this.displayNode); + } + }, + + save: function(/*Boolean*/ focus){ + // summary: + // Save the contents of the editor and revert to display mode. + // focus: Boolean + // Focus on the display mode text + // tags: + // private + + if(this.disabled || !this.editing){ return; } + this.editing = false; + + var ww = this.wrapperWidget; + var value = ww.getValue(); + this.set('value', value); // display changed, formatted value + + // tell the world that we have changed + setTimeout(dojo.hitch(this, "onChange", value), 0); // setTimeout prevents browser freeze for long-running event handlers + + this._showText(focus); // set focus as needed + }, + + setValue: function(/*String*/ val){ + // summary: + // Deprecated. Use set('value', ...) instead. + // tags: + // deprecated + dojo.deprecated("dijit.InlineEditBox.setValue() is deprecated. Use set('value', ...) instead.", "", "2.0"); + return this.set("value", val); + }, + + _setValueAttr: function(/*String*/ val){ + // summary: + // Hook to make set("value", ...) work. + // Inserts specified HTML value into this node, or an "input needed" character if node is blank. + + this.value = val = dojo.trim(val); + if(!this.renderAsHtml){ + val = val.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """).replace(/\n/g, "<br>"); + } + this.displayNode.innerHTML = val || this.noValueIndicator; + }, + + getValue: function(){ + // summary: + // Deprecated. Use get('value') instead. + // tags: + // deprecated + dojo.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use get('value') instead.", "", "2.0"); + return this.get("value"); + }, + + cancel: function(/*Boolean*/ focus){ + // summary: + // Revert to display mode, discarding any changes made in the editor + // tags: + // private + + if(this.disabled || !this.editing){ return; } + this.editing = false; + + // tell the world that we have no changes + setTimeout(dojo.hitch(this, "onCancel"), 0); // setTimeout prevents browser freeze for long-running event handlers + + this._showText(focus); + } +}); + +dojo.declare( + "dijit._InlineEditor", + [dijit._Widget, dijit._Templated], +{ + // summary: + // Internal widget used by InlineEditBox, displayed when in editing mode + // to display the editor and maybe save/cancel buttons. Calling code should + // connect to save/cancel methods to detect when editing is finished + // + // Has mainly the same parameters as InlineEditBox, plus these values: + // + // style: Object + // Set of CSS attributes of display node, to replicate in editor + // + // value: String + // Value as an HTML string or plain text string, depending on renderAsHTML flag + + templateString: dojo.cache("dijit", "templates/InlineEditBox.html", "<span dojoAttachPoint=\"editNode\" waiRole=\"presentation\" style=\"position: absolute; visibility:hidden\" class=\"dijitReset dijitInline\"\n\tdojoAttachEvent=\"onkeypress: _onKeyPress\"\n\t><span dojoAttachPoint=\"editorPlaceholder\"></span\n\t><span dojoAttachPoint=\"buttonContainer\"\n\t\t><button class='saveButton' dojoAttachPoint=\"saveButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:save\" label=\"${buttonSave}\"></button\n\t\t><button class='cancelButton' dojoAttachPoint=\"cancelButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:cancel\" label=\"${buttonCancel}\"></button\n\t></span\n></span>\n"), + widgetsInTemplate: true, + + postMixInProperties: function(){ + this.inherited(arguments); + this.messages = dojo.i18n.getLocalization("dijit", "common", this.lang); + dojo.forEach(["buttonSave", "buttonCancel"], function(prop){ + if(!this[prop]){ this[prop] = this.messages[prop]; } + }, this); + }, + + postCreate: function(){ + // Create edit widget in place in the template + var cls = dojo.getObject(this.editor); + + // Copy the style from the source + // Don't copy ALL properties though, just the necessary/applicable ones. + // wrapperStyle/destStyle code is to workaround IE bug where getComputedStyle().fontSize + // is a relative value like 200%, rather than an absolute value like 24px, and + // the 200% can refer *either* to a setting on the node or it's ancestor (see #11175) + var srcStyle = this.sourceStyle, + editStyle = "line-height:" + srcStyle.lineHeight + ";", + destStyle = dojo.getComputedStyle(this.domNode); + dojo.forEach(["Weight","Family","Size","Style"], function(prop){ + var textStyle = srcStyle["font"+prop], + wrapperStyle = destStyle["font"+prop]; + if(wrapperStyle != textStyle){ + editStyle += "font-"+prop+":"+srcStyle["font"+prop]+";"; + } + }, this); + dojo.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop){ + this.domNode.style[prop] = srcStyle[prop]; + }, this); + var width = this.inlineEditBox.width; + if(width == "100%"){ + // block mode + editStyle += "width:100%;"; + this.domNode.style.display = "block"; + }else{ + // inline-block mode + editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";"; + } + var editorParams = dojo.delegate(this.inlineEditBox.editorParams, { + style: editStyle, + dir: this.dir, + lang: this.lang + }); + editorParams[ "displayedValue" in cls.prototype ? "displayedValue" : "value"] = this.value; + var ew = (this.editWidget = new cls(editorParams, this.editorPlaceholder)); + + if(this.inlineEditBox.autoSave){ + // Remove the save/cancel buttons since saving is done by simply tabbing away or + // selecting a value from the drop down list + dojo.destroy(this.buttonContainer); + + // Selecting a value from a drop down list causes an onChange event and then we save + this.connect(ew, "onChange", "_onChange"); + + // ESC and TAB should cancel and save. Note that edit widgets do a stopEvent() on ESC key (to + // prevent Dialog from closing when the user just wants to revert the value in the edit widget), + // so this is the only way we can see the key press event. + this.connect(ew, "onKeyPress", "_onKeyPress"); + }else{ + // If possible, enable/disable save button based on whether the user has changed the value + if("intermediateChanges" in cls.prototype){ + ew.set("intermediateChanges", true); + this.connect(ew, "onChange", "_onIntermediateChange"); + this.saveButton.set("disabled", true); + } + } + }, + + _onIntermediateChange: function(val){ + // summary: + // Called for editor widgets that support the intermediateChanges=true flag as a way + // to detect when to enable/disabled the save button + this.saveButton.set("disabled", (this.getValue() == this._resetValue) || !this.enableSave()); + }, + + destroy: function(){ + this.editWidget.destroy(true); // let the parent wrapper widget clean up the DOM + this.inherited(arguments); + }, + + getValue: function(){ + // summary: + // Return the [display] value of the edit widget + var ew = this.editWidget; + return String(ew.get("displayedValue" in ew ? "displayedValue" : "value")); + }, + + _onKeyPress: function(e){ + // summary: + // Handler for keypress in the edit box in autoSave mode. + // description: + // For autoSave widgets, if Esc/Enter, call cancel/save. + // tags: + // private + + if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){ + if(e.altKey || e.ctrlKey){ return; } + // If Enter/Esc pressed, treat as save/cancel. + if(e.charOrCode == dojo.keys.ESCAPE){ + dojo.stopEvent(e); + this.cancel(true); // sets editing=false which short-circuits _onBlur processing + }else if(e.charOrCode == dojo.keys.ENTER && e.target.tagName == "INPUT"){ + dojo.stopEvent(e); + this._onChange(); // fire _onBlur and then save + } + + // _onBlur will handle TAB automatically by allowing + // the TAB to change focus before we mess with the DOM: #6227 + // Expounding by request: + // The current focus is on the edit widget input field. + // save() will hide and destroy this widget. + // We want the focus to jump from the currently hidden + // displayNode, but since it's hidden, it's impossible to + // unhide it, focus it, and then have the browser focus + // away from it to the next focusable element since each + // of these events is asynchronous and the focus-to-next-element + // is already queued. + // So we allow the browser time to unqueue the move-focus event + // before we do all the hide/show stuff. + } + }, + + _onBlur: function(){ + // summary: + // Called when focus moves outside the editor + // tags: + // private + + this.inherited(arguments); + if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){ + if(this.getValue() == this._resetValue){ + this.cancel(false); + }else if(this.enableSave()){ + this.save(false); + } + } + }, + + _onChange: function(){ + // summary: + // Called when the underlying widget fires an onChange event, + // such as when the user selects a value from the drop down list of a ComboBox, + // which means that the user has finished entering the value and we should save. + // tags: + // private + + if(this.inlineEditBox.autoSave && this.inlineEditBox.editing && this.enableSave()){ + dojo.style(this.inlineEditBox.displayNode, { display: "" }); + dijit.focus(this.inlineEditBox.displayNode); // fires _onBlur which will save the formatted value + } + }, + + enableSave: function(){ + // summary: + // User overridable function returning a Boolean to indicate + // if the Save button should be enabled or not - usually due to invalid conditions + // tags: + // extension + return ( + this.editWidget.isValid + ? this.editWidget.isValid() + : true + ); + }, + + focus: function(){ + // summary: + // Focus the edit widget. + // tags: + // protected + + this.editWidget.focus(); + setTimeout(dojo.hitch(this, function(){ + if(this.editWidget.focusNode && this.editWidget.focusNode.tagName == "INPUT"){ + dijit.selectInputText(this.editWidget.focusNode); + } + }), 0); + } +}); + +} + +if(!dojo._hasResource["dojo.cookie"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.cookie"] = true; +dojo.provide("dojo.cookie"); + + + +/*===== +dojo.__cookieProps = function(){ + // expires: Date|String|Number? + // If a number, the number of days from today at which the cookie + // will expire. If a date, the date past which the cookie will expire. + // If expires is in the past, the cookie will be deleted. + // If expires is omitted or is 0, the cookie will expire when the browser closes. << FIXME: 0 seems to disappear right away? FF3. + // path: String? + // The path to use for the cookie. + // domain: String? + // The domain to use for the cookie. + // secure: Boolean? + // Whether to only send the cookie on secure connections + this.expires = expires; + this.path = path; + this.domain = domain; + this.secure = secure; +} +=====*/ + + +dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/props){ + // summary: + // Get or set a cookie. + // description: + // If one argument is passed, returns the value of the cookie + // For two or more arguments, acts as a setter. + // name: + // Name of the cookie + // value: + // Value for the cookie + // props: + // Properties for the cookie + // example: + // set a cookie with the JSON-serialized contents of an object which + // will expire 5 days from now: + // | dojo.cookie("configObj", dojo.toJson(config), { expires: 5 }); + // + // example: + // de-serialize a cookie back into a JavaScript object: + // | var config = dojo.fromJson(dojo.cookie("configObj")); + // + // example: + // delete a cookie: + // | dojo.cookie("configObj", null, {expires: -1}); + var c = document.cookie; + if(arguments.length == 1){ + var matches = c.match(new RegExp("(?:^|; )" + dojo.regexp.escapeString(name) + "=([^;]*)")); + return matches ? decodeURIComponent(matches[1]) : undefined; // String or undefined + }else{ + props = props || {}; +// FIXME: expires=0 seems to disappear right away, not on close? (FF3) Change docs? + var exp = props.expires; + if(typeof exp == "number"){ + var d = new Date(); + d.setTime(d.getTime() + exp*24*60*60*1000); + exp = props.expires = d; + } + if(exp && exp.toUTCString){ props.expires = exp.toUTCString(); } + + value = encodeURIComponent(value); + var updatedCookie = name + "=" + value, propName; + for(propName in props){ + updatedCookie += "; " + propName; + var propValue = props[propName]; + if(propValue !== true){ updatedCookie += "=" + propValue; } + } + document.cookie = updatedCookie; + } +}; + +dojo.cookie.isSupported = function(){ + // summary: + // Use to determine if the current browser supports cookies or not. + // + // Returns true if user allows cookies. + // Returns false if user doesn't allow cookies. + + if(!("cookieEnabled" in navigator)){ + this("__djCookieTest__", "CookiesAllowed"); + navigator.cookieEnabled = this("__djCookieTest__") == "CookiesAllowed"; + if(navigator.cookieEnabled){ + this("__djCookieTest__", "", {expires: -1}); + } + } + return navigator.cookieEnabled; +}; + +} + +if(!dojo._hasResource["dijit.layout.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.StackController"] = true; +dojo.provide("dijit.layout.StackController"); + + + + + + + +dojo.declare( + "dijit.layout.StackController", + [dijit._Widget, dijit._Templated, dijit._Container], + { + // summary: + // Set of buttons to select a page in a page list. + // description: + // Monitors the specified StackContainer, and whenever a page is + // added, deleted, or selected, updates itself accordingly. + + templateString: "<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>", + + // containerId: [const] String + // The id of the page container that I point to + containerId: "", + + // buttonWidget: [const] String + // The name of the button widget to create to correspond to each page + buttonWidget: "dijit.layout._StackButton", + + postCreate: function(){ + dijit.setWaiRole(this.domNode, "tablist"); + + this.pane2button = {}; // mapping from pane id to buttons + this.pane2handles = {}; // mapping from pane id to this.connect() handles + + // Listen to notifications from StackContainer + this.subscribe(this.containerId+"-startup", "onStartup"); + this.subscribe(this.containerId+"-addChild", "onAddChild"); + this.subscribe(this.containerId+"-removeChild", "onRemoveChild"); + this.subscribe(this.containerId+"-selectChild", "onSelectChild"); + this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress"); + }, + + onStartup: function(/*Object*/ info){ + // summary: + // Called after StackContainer has finished initializing + // tags: + // private + dojo.forEach(info.children, this.onAddChild, this); + if(info.selected){ + // Show button corresponding to selected pane (unless selected + // is null because there are no panes) + this.onSelectChild(info.selected); + } + }, + + destroy: function(){ + for(var pane in this.pane2button){ + this.onRemoveChild(dijit.byId(pane)); + } + this.inherited(arguments); + }, + + onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){ + // summary: + // Called whenever a page is added to the container. + // Create button corresponding to the page. + // tags: + // private + + // create an instance of the button widget + var cls = dojo.getObject(this.buttonWidget); + var button = new cls({ + id: this.id + "_" + page.id, + label: page.title, + dir: page.dir, + lang: page.lang, + showLabel: page.showTitle, + iconClass: page.iconClass, + closeButton: page.closable, + title: page.tooltip + }); + dijit.setWaiState(button.focusNode,"selected", "false"); + this.pane2handles[page.id] = [ + this.connect(page, 'set', function(name, value){ + var buttonAttr = { + title: 'label', + showTitle: 'showLabel', + iconClass: 'iconClass', + closable: 'closeButton', + tooltip: 'title' + }[name]; + if(buttonAttr){ + button.set(buttonAttr, value); + } + }), + this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)), + this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page)) + ]; + this.addChild(button, insertIndex); + this.pane2button[page.id] = button; + page.controlButton = button; // this value might be overwritten if two tabs point to same container + if(!this._currentChild){ // put the first child into the tab order + button.focusNode.setAttribute("tabIndex", "0"); + dijit.setWaiState(button.focusNode, "selected", "true"); + this._currentChild = page; + } + // make sure all tabs have the same length + if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){ + this._rectifyRtlTabList(); + } + }, + + onRemoveChild: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever a page is removed from the container. + // Remove the button corresponding to the page. + // tags: + // private + + if(this._currentChild === page){ this._currentChild = null; } + dojo.forEach(this.pane2handles[page.id], this.disconnect, this); + delete this.pane2handles[page.id]; + var button = this.pane2button[page.id]; + if(button){ + this.removeChild(button); + delete this.pane2button[page.id]; + button.destroy(); + } + delete page.controlButton; + }, + + onSelectChild: function(/*dijit._Widget*/ page){ + // summary: + // Called when a page has been selected in the StackContainer, either by me or by another StackController + // tags: + // private + + if(!page){ return; } + + if(this._currentChild){ + var oldButton=this.pane2button[this._currentChild.id]; + oldButton.set('checked', false); + dijit.setWaiState(oldButton.focusNode, "selected", "false"); + oldButton.focusNode.setAttribute("tabIndex", "-1"); + } + + var newButton=this.pane2button[page.id]; + newButton.set('checked', true); + dijit.setWaiState(newButton.focusNode, "selected", "true"); + this._currentChild = page; + newButton.focusNode.setAttribute("tabIndex", "0"); + var container = dijit.byId(this.containerId); + dijit.setWaiState(container.containerNode, "labelledby", newButton.id); + }, + + onButtonClick: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever one of my child buttons is pressed in an attempt to select a page + // tags: + // private + + var container = dijit.byId(this.containerId); + container.selectChild(page); + }, + + onCloseButtonClick: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever one of my child buttons [X] is pressed in an attempt to close a page + // tags: + // private + + var container = dijit.byId(this.containerId); + container.closeChild(page); + if(this._currentChild){ + var b = this.pane2button[this._currentChild.id]; + if(b){ + dijit.focus(b.focusNode || b.domNode); + } + } + }, + + // TODO: this is a bit redundant with forward, back api in StackContainer + adjacent: function(/*Boolean*/ forward){ + // summary: + // Helper for onkeypress to find next/previous button + // tags: + // private + + if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; } + // find currently focused button in children array + var children = this.getChildren(); + var current = dojo.indexOf(children, this.pane2button[this._currentChild.id]); + // pick next button to focus on + var offset = forward ? 1 : children.length - 1; + return children[ (current + offset) % children.length ]; // dijit._Widget + }, + + onkeypress: function(/*Event*/ e){ + // summary: + // Handle keystrokes on the page list, for advancing to next/previous button + // and closing the current page if the page is closable. + // tags: + // private + + if(this.disabled || e.altKey ){ return; } + var forward = null; + if(e.ctrlKey || !e._djpage){ + var k = dojo.keys; + switch(e.charOrCode){ + case k.LEFT_ARROW: + case k.UP_ARROW: + if(!e._djpage){ forward = false; } + break; + case k.PAGE_UP: + if(e.ctrlKey){ forward = false; } + break; + case k.RIGHT_ARROW: + case k.DOWN_ARROW: + if(!e._djpage){ forward = true; } + break; + case k.PAGE_DOWN: + if(e.ctrlKey){ forward = true; } + break; + case k.DELETE: + if(this._currentChild.closable){ + this.onCloseButtonClick(this._currentChild); + } + dojo.stopEvent(e); + break; + default: + if(e.ctrlKey){ + if(e.charOrCode === k.TAB){ + this.adjacent(!e.shiftKey).onClick(); + dojo.stopEvent(e); + }else if(e.charOrCode == "w"){ + if(this._currentChild.closable){ + this.onCloseButtonClick(this._currentChild); + } + dojo.stopEvent(e); // avoid browser tab closing. + } + } + } + // handle page navigation + if(forward !== null){ + this.adjacent(forward).onClick(); + dojo.stopEvent(e); + } + } + }, + + onContainerKeyPress: function(/*Object*/ info){ + // summary: + // Called when there was a keypress on the container + // tags: + // private + info.e._djpage = info.page; + this.onkeypress(info.e); + } + }); + + +dojo.declare("dijit.layout._StackButton", + dijit.form.ToggleButton, + { + // summary: + // Internal widget used by StackContainer. + // description: + // The button-like or tab-like object you click to select or delete a page + // tags: + // private + + // Override _FormWidget.tabIndex. + // StackContainer buttons are not in the tab order by default. + // Probably we should be calling this.startupKeyNavChildren() instead. + tabIndex: "-1", + + postCreate: function(/*Event*/ evt){ + dijit.setWaiRole((this.focusNode || this.domNode), "tab"); + this.inherited(arguments); + }, + + onClick: function(/*Event*/ evt){ + // summary: + // This is for TabContainer where the tabs are <span> rather than button, + // so need to set focus explicitly (on some browsers) + // Note that you shouldn't override this method, but you can connect to it. + dijit.focus(this.focusNode); + + // ... now let StackController catch the event and tell me what to do + }, + + onClickCloseButton: function(/*Event*/ evt){ + // summary: + // StackContainer connects to this function; if your widget contains a close button + // then clicking it should call this function. + // Note that you shouldn't override this method, but you can connect to it. + evt.stopPropagation(); + } + }); + + +} + +if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.StackContainer"] = true; +dojo.provide("dijit.layout.StackContainer"); + + + + + + +dojo.declare( + "dijit.layout.StackContainer", + dijit.layout._LayoutWidget, + { + // summary: + // A container that has multiple children, but shows only + // one child at a time + // + // description: + // A container for widgets (ContentPanes, for example) That displays + // only one Widget at a time. + // + // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild + // + // Can be base class for container, Wizard, Show, etc. + + // doLayout: Boolean + // If true, change the size of my currently displayed child to match my size + doLayout: true, + + // persist: Boolean + // Remembers the selected child across sessions + persist: false, + + baseClass: "dijitStackContainer", + +/*===== + // selectedChildWidget: [readonly] dijit._Widget + // References the currently selected child widget, if any. + // Adjust selected child with selectChild() method. + selectedChildWidget: null, +=====*/ + + postCreate: function(){ + this.inherited(arguments); + dojo.addClass(this.domNode, "dijitLayoutContainer"); + dijit.setWaiRole(this.containerNode, "tabpanel"); + this.connect(this.domNode, "onkeypress", this._onKeyPress); + }, + + startup: function(){ + if(this._started){ return; } + + var children = this.getChildren(); + + // Setup each page panel to be initially hidden + dojo.forEach(children, this._setupChild, this); + + // Figure out which child to initially display, defaulting to first one + if(this.persist){ + this.selectedChildWidget = dijit.byId(dojo.cookie(this.id + "_selectedChild")); + }else{ + dojo.some(children, function(child){ + if(child.selected){ + this.selectedChildWidget = child; + } + return child.selected; + }, this); + } + var selected = this.selectedChildWidget; + if(!selected && children[0]){ + selected = this.selectedChildWidget = children[0]; + selected.selected = true; + } + + // Publish information about myself so any StackControllers can initialize. + // This needs to happen before this.inherited(arguments) so that for + // TabContainer, this._contentBox doesn't include the space for the tab labels. + dojo.publish(this.id+"-startup", [{children: children, selected: selected}]); + + // Startup each child widget, and do initial layout like setting this._contentBox, + // then calls this.resize() which does the initial sizing on the selected child. + this.inherited(arguments); + }, + + resize: function(){ + // Resize is called when we are first made visible (it's called from startup() + // if we are initially visible). If this is the first time we've been made + // visible then show our first child. + var selected = this.selectedChildWidget; + if(selected && !this._hasBeenShown){ + this._hasBeenShown = true; + this._showChild(selected); + } + this.inherited(arguments); + }, + + _setupChild: function(/*dijit._Widget*/ child){ + // Overrides _LayoutWidget._setupChild() + + this.inherited(arguments); + + dojo.removeClass(child.domNode, "dijitVisible"); + dojo.addClass(child.domNode, "dijitHidden"); + + // remove the title attribute so it doesn't show up when i hover + // over a node + child.domNode.title = ""; + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Overrides _Container.addChild() to do layout and publish events + + this.inherited(arguments); + + if(this._started){ + dojo.publish(this.id+"-addChild", [child, insertIndex]); + + // in case the tab titles have overflowed from one line to two lines + // (or, if this if first child, from zero lines to one line) + // TODO: w/ScrollingTabController this is no longer necessary, although + // ScrollTabController.resize() does need to get called to show/hide + // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild() + this.layout(); + + // if this is the first child, then select it + if(!this.selectedChildWidget){ + this.selectChild(child); + } + } + }, + + removeChild: function(/*dijit._Widget*/ page){ + // Overrides _Container.removeChild() to do layout and publish events + + this.inherited(arguments); + + if(this._started){ + // this will notify any tablists to remove a button; do this first because it may affect sizing + dojo.publish(this.id + "-removeChild", [page]); + } + + // If we are being destroyed than don't run the code below (to select another page), because we are deleting + // every page one by one + if(this._beingDestroyed){ return; } + + // Select new page to display, also updating TabController to show the respective tab. + // Do this before layout call because it can affect the height of the TabController. + if(this.selectedChildWidget === page){ + this.selectedChildWidget = undefined; + if(this._started){ + var children = this.getChildren(); + if(children.length){ + this.selectChild(children[0]); + } + } + } + + if(this._started){ + // In case the tab titles now take up one line instead of two lines + // (note though that ScrollingTabController never overflows to multiple lines), + // or the height has changed slightly because of addition/removal of tab which close icon + this.layout(); + } + }, + + selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){ + // summary: + // Show the given widget (which must be one of my children) + // page: + // Reference to child widget or id of child widget + + page = dijit.byId(page); + + if(this.selectedChildWidget != page){ + // Deselect old page and select new one + this._transition(page, this.selectedChildWidget, animate); + this.selectedChildWidget = page; + dojo.publish(this.id+"-selectChild", [page]); + + if(this.persist){ + dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id); + } + } + }, + + _transition: function(/*dijit._Widget*/newWidget, /*dijit._Widget*/oldWidget){ + // summary: + // Hide the old widget and display the new widget. + // Subclasses should override this. + // tags: + // protected extension + if(oldWidget){ + this._hideChild(oldWidget); + } + this._showChild(newWidget); + + // Size the new widget, in case this is the first time it's being shown, + // or I have been resized since the last time it was shown. + // Note that page must be visible for resizing to work. + if(newWidget.resize){ + if(this.doLayout){ + newWidget.resize(this._containerContentBox || this._contentBox); + }else{ + // the child should pick it's own size but we still need to call resize() + // (with no arguments) to let the widget lay itself out + newWidget.resize(); + } + } + }, + + _adjacent: function(/*Boolean*/ forward){ + // summary: + // Gets the next/previous child widget in this container from the current selection. + var children = this.getChildren(); + var index = dojo.indexOf(children, this.selectedChildWidget); + index += forward ? 1 : children.length - 1; + return children[ index % children.length ]; // dijit._Widget + }, + + forward: function(){ + // summary: + // Advance to next page. + this.selectChild(this._adjacent(true), true); + }, + + back: function(){ + // summary: + // Go back to previous page. + this.selectChild(this._adjacent(false), true); + }, + + _onKeyPress: function(e){ + dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]); + }, + + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){ + this.selectedChildWidget.resize(this._containerContentBox || this._contentBox); + } + }, + + _showChild: function(/*dijit._Widget*/ page){ + // summary: + // Show the specified child by changing it's CSS, and call _onShow()/onShow() so + // it can do any updates it needs regarding loading href's etc. + var children = this.getChildren(); + page.isFirstChild = (page == children[0]); + page.isLastChild = (page == children[children.length-1]); + page.selected = true; + + dojo.removeClass(page.domNode, "dijitHidden"); + dojo.addClass(page.domNode, "dijitVisible"); + + page._onShow(); + }, + + _hideChild: function(/*dijit._Widget*/ page){ + // summary: + // Hide the specified child by changing it's CSS, and call _onHide() so + // it's notified. + page.selected=false; + dojo.removeClass(page.domNode, "dijitVisible"); + dojo.addClass(page.domNode, "dijitHidden"); + + page.onHide(); + }, + + closeChild: function(/*dijit._Widget*/ page){ + // summary: + // Callback when user clicks the [X] to remove a page. + // If onClose() returns true then remove and destroy the child. + // tags: + // private + var remove = page.onClose(this, page); + if(remove){ + this.removeChild(page); + // makes sure we can clean up executeScripts in ContentPane onUnLoad + page.destroyRecursive(); + } + }, + + destroyDescendants: function(/*Boolean*/preserveDom){ + dojo.forEach(this.getChildren(), function(child){ + this.removeChild(child); + child.destroyRecursive(preserveDom); + }, this); + } +}); + +// For back-compat, remove for 2.0 + + + +// These arguments can be specified for the children of a StackContainer. +// Since any widget can be specified as a StackContainer child, mix them +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // selected: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // Specifies that this widget should be the initially displayed pane. + // Note: to change the selected child use `dijit.layout.StackContainer.selectChild` + selected: false, + + // closable: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // True if user can close (destroy) this child, such as (for example) clicking the X on the tab. + closable: false, + + // iconClass: String + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // CSS Class specifying icon to use in label associated with this pane. + iconClass: "", + + // showTitle: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // When true, display title of this widget as tab label etc., rather than just using + // icon specified in iconClass + showTitle: true +}); + +} + +if(!dojo._hasResource["dijit.layout.AccordionPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.AccordionPane"] = true; +dojo.provide("dijit.layout.AccordionPane"); + + + +dojo.declare("dijit.layout.AccordionPane", dijit.layout.ContentPane, { + // summary: + // Deprecated widget. Use `dijit.layout.ContentPane` instead. + // tags: + // deprecated + + constructor: function(){ + dojo.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0"); + }, + + onSelected: function(){ + // summary: + // called when this pane is selected + } +}); + +} + +if(!dojo._hasResource["dijit.layout.AccordionContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.AccordionContainer"] = true; +dojo.provide("dijit.layout.AccordionContainer"); + + + + + + + + + + // for back compat, remove for 2.0 + +dojo.declare( + "dijit.layout.AccordionContainer", + dijit.layout.StackContainer, + { + // summary: + // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time, + // and switching between panes is visualized by sliding the other panes up/down. + // example: + // | <div dojoType="dijit.layout.AccordionContainer"> + // | <div dojoType="dijit.layout.ContentPane" title="pane 1"> + // | </div> + // | <div dojoType="dijit.layout.ContentPane" title="pane 2"> + // | <p>This is some text</p> + // | </div> + // | </div> + + // duration: Integer + // Amount of time (in ms) it takes to slide panes + duration: dijit.defaultDuration, + + // buttonWidget: [const] String + // The name of the widget used to display the title of each pane + buttonWidget: "dijit.layout._AccordionButton", + + // _verticalSpace: Number + // Pixels of space available for the open pane + // (my content box size minus the cumulative size of all the title bars) + _verticalSpace: 0, + + baseClass: "dijitAccordionContainer", + + postCreate: function(){ + this.domNode.style.overflow = "hidden"; + this.inherited(arguments); + dijit.setWaiRole(this.domNode, "tablist"); + }, + + startup: function(){ + if(this._started){ return; } + this.inherited(arguments); + if(this.selectedChildWidget){ + var style = this.selectedChildWidget.containerNode.style; + style.display = ""; + style.overflow = "auto"; + this.selectedChildWidget._wrapperWidget.set("selected", true); + } + }, + + _getTargetHeight: function(/* Node */ node){ + // summary: + // For the given node, returns the height that should be + // set to achieve our vertical space (subtract any padding + // we may have). + // + // This is used by the animations. + // + // TODO: I don't think this works correctly in IE quirks when an elements + // style.height including padding and borders + var cs = dojo.getComputedStyle(node); + return Math.max(this._verticalSpace - dojo._getPadBorderExtents(node, cs).h - dojo._getMarginExtents(node, cs).h, 0); + }, + + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + // Set the height of the open pane based on what room remains. + + var openPane = this.selectedChildWidget; + + if(!openPane){ return;} + + var openPaneContainer = openPane._wrapperWidget.domNode, + openPaneContainerMargin = dojo._getMarginExtents(openPaneContainer), + openPaneContainerPadBorder = dojo._getPadBorderExtents(openPaneContainer), + mySize = this._contentBox; + + // get cumulative height of all the unselected title bars + var totalCollapsedHeight = 0; + dojo.forEach(this.getChildren(), function(child){ + if(child != openPane){ + totalCollapsedHeight += dojo.marginBox(child._wrapperWidget.domNode).h; + } + }); + this._verticalSpace = mySize.h - totalCollapsedHeight - openPaneContainerMargin.h + - openPaneContainerPadBorder.h - openPane._buttonWidget.getTitleHeight(); + + // Memo size to make displayed child + this._containerContentBox = { + h: this._verticalSpace, + w: this._contentBox.w - openPaneContainerMargin.w - openPaneContainerPadBorder.w + }; + + if(openPane){ + openPane.resize(this._containerContentBox); + } + }, + + _setupChild: function(child){ + // Overrides _LayoutWidget._setupChild(). + // Put wrapper widget around the child widget, showing title + + child._wrapperWidget = new dijit.layout._AccordionInnerContainer({ + contentWidget: child, + buttonWidget: this.buttonWidget, + id: child.id + "_wrapper", + dir: child.dir, + lang: child.lang, + parent: this + }); + + this.inherited(arguments); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + if(this._started){ + // Adding a child to a started Accordion is complicated because children have + // wrapper widgets. Default code path (calling this.inherited()) would add + // the new child inside another child's wrapper. + + // First add in child as a direct child of this AccordionContainer + dojo.place(child.domNode, this.containerNode, insertIndex); + + if(!child._started){ + child.startup(); + } + + // Then stick the wrapper widget around the child widget + this._setupChild(child); + + // Code below copied from StackContainer + dojo.publish(this.id+"-addChild", [child, insertIndex]); + this.layout(); + if(!this.selectedChildWidget){ + this.selectChild(child); + } + }else{ + // We haven't been started yet so just add in the child widget directly, + // and the wrapper will be created on startup() + this.inherited(arguments); + } + }, + + removeChild: function(child){ + // Overrides _LayoutWidget.removeChild(). + + // destroy wrapper widget first, before StackContainer.getChildren() call + child._wrapperWidget.destroy(); + delete child._wrapperWidget; + dojo.removeClass(child.domNode, "dijitHidden"); + + this.inherited(arguments); + }, + + getChildren: function(){ + // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes + return dojo.map(this.inherited(arguments), function(child){ + return child.declaredClass == "dijit.layout._AccordionInnerContainer" ? child.contentWidget : child; + }, this); + }, + + destroy: function(){ + dojo.forEach(this.getChildren(), function(child){ + child._wrapperWidget.destroy(); + }); + this.inherited(arguments); + }, + + _transition: function(/*dijit._Widget?*/newWidget, /*dijit._Widget?*/oldWidget, /*Boolean*/ animate){ + // Overrides StackContainer._transition() to provide sliding of title bars etc. + +//TODO: should be able to replace this with calls to slideIn/slideOut + if(this._inTransition){ return; } + var animations = []; + var paneHeight = this._verticalSpace; + if(newWidget){ + newWidget._wrapperWidget.set("selected", true); + + this._showChild(newWidget); // prepare widget to be slid in + + // Size the new widget, in case this is the first time it's being shown, + // or I have been resized since the last time it was shown. + // Note that page must be visible for resizing to work. + if(this.doLayout && newWidget.resize){ + newWidget.resize(this._containerContentBox); + } + + var newContents = newWidget.domNode; + dojo.addClass(newContents, "dijitVisible"); + dojo.removeClass(newContents, "dijitHidden"); + + if(animate){ + var newContentsOverflow = newContents.style.overflow; + newContents.style.overflow = "hidden"; + animations.push(dojo.animateProperty({ + node: newContents, + duration: this.duration, + properties: { + height: { start: 1, end: this._getTargetHeight(newContents) } + }, + onEnd: function(){ + newContents.style.overflow = newContentsOverflow; + + // Kick IE to workaround layout bug, see #11415 + if(dojo.isIE){ + setTimeout(function(){ + dojo.removeClass(newContents.parentNode, "dijitAccordionInnerContainerFocused"); + setTimeout(function(){ + dojo.addClass(newContents.parentNode, "dijitAccordionInnerContainerFocused"); + }, 0); + }, 0); + } + } + })); + } + } + if(oldWidget){ + oldWidget._wrapperWidget.set("selected", false); + var oldContents = oldWidget.domNode; + if(animate){ + var oldContentsOverflow = oldContents.style.overflow; + oldContents.style.overflow = "hidden"; + animations.push(dojo.animateProperty({ + node: oldContents, + duration: this.duration, + properties: { + height: { start: this._getTargetHeight(oldContents), end: 1 } + }, + onEnd: function(){ + dojo.addClass(oldContents, "dijitHidden"); + dojo.removeClass(oldContents, "dijitVisible"); + oldContents.style.overflow = oldContentsOverflow; + if(oldWidget.onHide){ + oldWidget.onHide(); + } + } + })); + }else{ + dojo.addClass(oldContents, "dijitHidden"); + dojo.removeClass(oldContents, "dijitVisible"); + if(oldWidget.onHide){ + oldWidget.onHide(); + } + } + } + + if(animate){ + this._inTransition = true; + var combined = dojo.fx.combine(animations); + combined.onEnd = dojo.hitch(this, function(){ + delete this._inTransition; + }); + combined.play(); + } + }, + + // note: we are treating the container as controller here + _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){ + // summary: + // Handle keypress events + // description: + // This is called from a handler on AccordionContainer.domNode + // (setup in StackContainer), and is also called directly from + // the click handler for accordion labels + if(this._inTransition || this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){ + if(this._inTransition){ + dojo.stopEvent(e); + } + return; + } + var k = dojo.keys, + c = e.charOrCode; + if((fromTitle && (c == k.LEFT_ARROW || c == k.UP_ARROW)) || + (e.ctrlKey && c == k.PAGE_UP)){ + this._adjacent(false)._buttonWidget._onTitleClick(); + dojo.stopEvent(e); + }else if((fromTitle && (c == k.RIGHT_ARROW || c == k.DOWN_ARROW)) || + (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){ + this._adjacent(true)._buttonWidget._onTitleClick(); + dojo.stopEvent(e); + } + } + } +); + +dojo.declare("dijit.layout._AccordionInnerContainer", + [dijit._Widget, dijit._CssStateMixin], { + // summary: + // Internal widget placed as direct child of AccordionContainer.containerNode. + // When other widgets are added as children to an AccordionContainer they are wrapped in + // this widget. + + // buttonWidget: String + // Name of class to use to instantiate title + // (Wish we didn't have a separate widget for just the title but maintaining it + // for backwards compatibility, is it worth it?) +/*===== + buttonWidget: null, +=====*/ + // contentWidget: dijit._Widget + // Pointer to the real child widget +/*===== + contentWidget: null, +=====*/ + + baseClass: "dijitAccordionInnerContainer", + + // tell nested layout widget that we will take care of sizing + isContainer: true, + isLayoutContainer: true, + + buildRendering: function(){ + // Create wrapper div, placed where the child is now + this.domNode = dojo.place("<div class='" + this.baseClass + "'>", this.contentWidget.domNode, "after"); + + // wrapper div's first child is the button widget (ie, the title bar) + var child = this.contentWidget, + cls = dojo.getObject(this.buttonWidget); + this.button = child._buttonWidget = (new cls({ + contentWidget: child, + label: child.title, + title: child.tooltip, + dir: child.dir, + lang: child.lang, + iconClass: child.iconClass, + id: child.id + "_button", + parent: this.parent + })).placeAt(this.domNode); + + // and then the actual content widget (changing it from prior-sibling to last-child) + dojo.place(this.contentWidget.domNode, this.domNode); + }, + + postCreate: function(){ + this.inherited(arguments); + this.connect(this.contentWidget, 'set', function(name, value){ + var mappedName = {title: "label", tooltip: "title", iconClass: "iconClass"}[name]; + if(mappedName){ + this.button.set(mappedName, value); + } + }, this); + }, + + _setSelectedAttr: function(/*Boolean*/ isSelected){ + this.selected = isSelected; + this.button.set("selected", isSelected); + if(isSelected){ + var cw = this.contentWidget; + if(cw.onSelected){ cw.onSelected(); } + } + }, + + startup: function(){ + // Called by _Container.addChild() + this.contentWidget.startup(); + }, + + destroy: function(){ + this.button.destroyRecursive(); + + delete this.contentWidget._buttonWidget; + delete this.contentWidget._wrapperWidget; + + this.inherited(arguments); + }, + + destroyDescendants: function(){ + // since getChildren isn't working for me, have to code this manually + this.contentWidget.destroyRecursive(); + } +}); + +dojo.declare("dijit.layout._AccordionButton", + [dijit._Widget, dijit._Templated, dijit._CssStateMixin], + { + // summary: + // The title bar to click to open up an accordion pane. + // Internal widget used by AccordionContainer. + // tags: + // private + + templateString: dojo.cache("dijit.layout", "templates/AccordionButton.html", "<div dojoAttachEvent='onclick:_onTitleClick' class='dijitAccordionTitle'>\n\t<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' wairole=\"tab\" waiState=\"expanded-false\"\n\t\t><span class='dijitInline dijitAccordionArrow' waiRole=\"presentation\"></span\n\t\t><span class='arrowTextUp' waiRole=\"presentation\">+</span\n\t\t><span class='arrowTextDown' waiRole=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" waiRole=\"presentation\"/>\n\t\t<span waiRole=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"), + attributeMap: dojo.mixin(dojo.clone(dijit.layout.ContentPane.prototype.attributeMap), { + label: {node: "titleTextNode", type: "innerHTML" }, + title: {node: "titleTextNode", type: "attribute", attribute: "title"}, + iconClass: { node: "iconNode", type: "class" } + }), + + baseClass: "dijitAccordionTitle", + + getParent: function(){ + // summary: + // Returns the AccordionContainer parent. + // tags: + // private + return this.parent; + }, + + postCreate: function(){ + this.inherited(arguments); + dojo.setSelectable(this.domNode, false); + var titleTextNodeId = dojo.attr(this.domNode,'id').replace(' ','_'); + dojo.attr(this.titleTextNode, "id", titleTextNodeId+"_title"); + dijit.setWaiState(this.focusNode, "labelledby", dojo.attr(this.titleTextNode, "id")); + }, + + getTitleHeight: function(){ + // summary: + // Returns the height of the title dom node. + return dojo.marginBox(this.domNode).h; // Integer + }, + + // TODO: maybe the parent should set these methods directly rather than forcing the code + // into the button widget? + _onTitleClick: function(){ + // summary: + // Callback when someone clicks my title. + var parent = this.getParent(); + if(!parent._inTransition){ + parent.selectChild(this.contentWidget, true); + dijit.focus(this.focusNode); + } + }, + + _onTitleKeyPress: function(/*Event*/ evt){ + return this.getParent()._onKeyPress(evt, this.contentWidget); + }, + + _setSelectedAttr: function(/*Boolean*/ isSelected){ + this.selected = isSelected; + dijit.setWaiState(this.focusNode, "expanded", isSelected); + dijit.setWaiState(this.focusNode, "selected", isSelected); + this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1"); + } +}); + +} + +if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.BorderContainer"] = true; +dojo.provide("dijit.layout.BorderContainer"); + + + + +dojo.declare( + "dijit.layout.BorderContainer", + dijit.layout._LayoutWidget, +{ + // summary: + // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. + // + // description: + // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;", + // that contains a child widget marked region="center" and optionally children widgets marked + // region equal to "top", "bottom", "leading", "trailing", "left" or "right". + // Children along the edges will be laid out according to width or height dimensions and may + // include optional splitters (splitter="true") to make them resizable by the user. The remaining + // space is designated for the center region. + // + // NOTE: Splitters must not be more than 50 pixels in width. + // + // The outer size must be specified on the BorderContainer node. Width must be specified for the sides + // and height for the top and bottom, respectively. No dimensions should be specified on the center; + // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like + // "left" and "right" except that they will be reversed in right-to-left environments. + // + // example: + // | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false" + // | style="width: 400px; height: 300px;"> + // | <div dojoType="ContentPane" region="top">header text</div> + // | <div dojoType="ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div> + // | <div dojoType="ContentPane" region="center">client area</div> + // | </div> + + // design: String + // Which design is used for the layout: + // - "headline" (default) where the top and bottom extend + // the full width of the container + // - "sidebar" where the left and right sides extend from top to bottom. + design: "headline", + + // gutters: Boolean + // Give each pane a border and margin. + // Margin determined by domNode.paddingLeft. + // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing. + gutters: true, + + // liveSplitters: Boolean + // Specifies whether splitters resize as you drag (true) or only upon mouseup (false) + liveSplitters: true, + + // persist: Boolean + // Save splitter positions in a cookie. + persist: false, + + baseClass: "dijitBorderContainer", + + // _splitterClass: String + // Optional hook to override the default Splitter widget used by BorderContainer + _splitterClass: "dijit.layout._Splitter", + + postMixInProperties: function(){ + // change class name to indicate that BorderContainer is being used purely for + // layout (like LayoutContainer) rather than for pretty formatting. + if(!this.gutters){ + this.baseClass += "NoGutter"; + } + this.inherited(arguments); + }, + + postCreate: function(){ + this.inherited(arguments); + + this._splitters = {}; + this._splitterThickness = {}; + }, + + startup: function(){ + if(this._started){ return; } + dojo.forEach(this.getChildren(), this._setupChild, this); + this.inherited(arguments); + }, + + _setupChild: function(/*dijit._Widget*/ child){ + // Override _LayoutWidget._setupChild(). + + var region = child.region; + if(region){ + this.inherited(arguments); + + dojo.addClass(child.domNode, this.baseClass+"Pane"); + + var ltr = this.isLeftToRight(); + if(region == "leading"){ region = ltr ? "left" : "right"; } + if(region == "trailing"){ region = ltr ? "right" : "left"; } + + //FIXME: redundant? + this["_"+region] = child.domNode; + this["_"+region+"Widget"] = child; + + // Create draggable splitter for resizing pane, + // or alternately if splitter=false but BorderContainer.gutters=true then + // insert dummy div just for spacing + if((child.splitter || this.gutters) && !this._splitters[region]){ + var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter"); + var splitter = new _Splitter({ + id: child.id + "_splitter", + container: this, + child: child, + region: region, + live: this.liveSplitters + }); + splitter.isSplitter = true; + this._splitters[region] = splitter.domNode; + dojo.place(this._splitters[region], child.domNode, "after"); + + // Splitters arent added as Contained children, so we need to call startup explicitly + splitter.startup(); + } + child.region = region; + } + }, + + _computeSplitterThickness: function(region){ + this._splitterThickness[region] = this._splitterThickness[region] || + dojo.marginBox(this._splitters[region])[(/top|bottom/.test(region) ? 'h' : 'w')]; + }, + + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + for(var region in this._splitters){ this._computeSplitterThickness(region); } + this._layoutChildren(); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Override _LayoutWidget.addChild(). + this.inherited(arguments); + if(this._started){ + this.layout(); //OPT + } + }, + + removeChild: function(/*dijit._Widget*/ child){ + // Override _LayoutWidget.removeChild(). + var region = child.region; + var splitter = this._splitters[region]; + if(splitter){ + dijit.byNode(splitter).destroy(); + delete this._splitters[region]; + delete this._splitterThickness[region]; + } + this.inherited(arguments); + delete this["_"+region]; + delete this["_" +region+"Widget"]; + if(this._started){ + this._layoutChildren(); + } + dojo.removeClass(child.domNode, this.baseClass+"Pane"); + }, + + getChildren: function(){ + // Override _LayoutWidget.getChildren() to only return real children, not the splitters. + return dojo.filter(this.inherited(arguments), function(widget){ + return !widget.isSplitter; + }); + }, + + getSplitter: function(/*String*/region){ + // summary: + // Returns the widget responsible for rendering the splitter associated with region + var splitter = this._splitters[region]; + return splitter ? dijit.byNode(splitter) : null; + }, + + resize: function(newSize, currentSize){ + // Overrides _LayoutWidget.resize(). + + // resetting potential padding to 0px to provide support for 100% width/height + padding + // TODO: this hack doesn't respect the box model and is a temporary fix + if(!this.cs || !this.pe){ + var node = this.domNode; + this.cs = dojo.getComputedStyle(node); + this.pe = dojo._getPadExtents(node, this.cs); + this.pe.r = dojo._toPixelValue(node, this.cs.paddingRight); + this.pe.b = dojo._toPixelValue(node, this.cs.paddingBottom); + + dojo.style(node, "padding", "0px"); + } + + this.inherited(arguments); + }, + + _layoutChildren: function(/*String?*/changedRegion, /*Number?*/ changedRegionSize){ + // summary: + // This is the main routine for setting size/position of each child. + // description: + // With no arguments, measures the height of top/bottom panes, the width + // of left/right panes, and then sizes all panes accordingly. + // + // With changedRegion specified (as "left", "top", "bottom", or "right"), + // it changes that region's width/height to changedRegionSize and + // then resizes other regions that were affected. + // changedRegion: + // The region should be changed because splitter was dragged. + // "left", "right", "top", or "bottom". + // changedRegionSize: + // The new width/height (in pixels) to make changedRegion + + if(!this._borderBox || !this._borderBox.h){ + // We are currently hidden, or we haven't been sized by our parent yet. + // Abort. Someone will resize us later. + return; + } + + var sidebarLayout = (this.design == "sidebar"); + var topHeight = 0, bottomHeight = 0, leftWidth = 0, rightWidth = 0; + var topStyle = {}, leftStyle = {}, rightStyle = {}, bottomStyle = {}, + centerStyle = (this._center && this._center.style) || {}; + + var changedSide = /left|right/.test(changedRegion); + + var layoutSides = !changedRegion || (!changedSide && !sidebarLayout); + var layoutTopBottom = !changedRegion || (changedSide && sidebarLayout); + + // Ask browser for width/height of side panes. + // Would be nice to cache this but height can change according to width + // (because words wrap around). I don't think width will ever change though + // (except when the user drags a splitter). + if(this._top){ + topStyle = (changedRegion == "top" || layoutTopBottom) && this._top.style; + topHeight = changedRegion == "top" ? changedRegionSize : dojo.marginBox(this._top).h; + } + if(this._left){ + leftStyle = (changedRegion == "left" || layoutSides) && this._left.style; + leftWidth = changedRegion == "left" ? changedRegionSize : dojo.marginBox(this._left).w; + } + if(this._right){ + rightStyle = (changedRegion == "right" || layoutSides) && this._right.style; + rightWidth = changedRegion == "right" ? changedRegionSize : dojo.marginBox(this._right).w; + } + if(this._bottom){ + bottomStyle = (changedRegion == "bottom" || layoutTopBottom) && this._bottom.style; + bottomHeight = changedRegion == "bottom" ? changedRegionSize : dojo.marginBox(this._bottom).h; + } + + var splitters = this._splitters; + var topSplitter = splitters.top, bottomSplitter = splitters.bottom, + leftSplitter = splitters.left, rightSplitter = splitters.right; + var splitterThickness = this._splitterThickness; + var topSplitterThickness = splitterThickness.top || 0, + leftSplitterThickness = splitterThickness.left || 0, + rightSplitterThickness = splitterThickness.right || 0, + bottomSplitterThickness = splitterThickness.bottom || 0; + + // Check for race condition where CSS hasn't finished loading, so + // the splitter width == the viewport width (#5824) + if(leftSplitterThickness > 50 || rightSplitterThickness > 50){ + setTimeout(dojo.hitch(this, function(){ + // Results are invalid. Clear them out. + this._splitterThickness = {}; + + for(var region in this._splitters){ + this._computeSplitterThickness(region); + } + this._layoutChildren(); + }), 50); + return false; + } + + var pe = this.pe; + + var splitterBounds = { + left: (sidebarLayout ? leftWidth + leftSplitterThickness: 0) + pe.l + "px", + right: (sidebarLayout ? rightWidth + rightSplitterThickness: 0) + pe.r + "px" + }; + + if(topSplitter){ + dojo.mixin(topSplitter.style, splitterBounds); + topSplitter.style.top = topHeight + pe.t + "px"; + } + + if(bottomSplitter){ + dojo.mixin(bottomSplitter.style, splitterBounds); + bottomSplitter.style.bottom = bottomHeight + pe.b + "px"; + } + + splitterBounds = { + top: (sidebarLayout ? 0 : topHeight + topSplitterThickness) + pe.t + "px", + bottom: (sidebarLayout ? 0 : bottomHeight + bottomSplitterThickness) + pe.b + "px" + }; + + if(leftSplitter){ + dojo.mixin(leftSplitter.style, splitterBounds); + leftSplitter.style.left = leftWidth + pe.l + "px"; + } + + if(rightSplitter){ + dojo.mixin(rightSplitter.style, splitterBounds); + rightSplitter.style.right = rightWidth + pe.r + "px"; + } + + dojo.mixin(centerStyle, { + top: pe.t + topHeight + topSplitterThickness + "px", + left: pe.l + leftWidth + leftSplitterThickness + "px", + right: pe.r + rightWidth + rightSplitterThickness + "px", + bottom: pe.b + bottomHeight + bottomSplitterThickness + "px" + }); + + var bounds = { + top: sidebarLayout ? pe.t + "px" : centerStyle.top, + bottom: sidebarLayout ? pe.b + "px" : centerStyle.bottom + }; + dojo.mixin(leftStyle, bounds); + dojo.mixin(rightStyle, bounds); + leftStyle.left = pe.l + "px"; rightStyle.right = pe.r + "px"; topStyle.top = pe.t + "px"; bottomStyle.bottom = pe.b + "px"; + if(sidebarLayout){ + topStyle.left = bottomStyle.left = leftWidth + leftSplitterThickness + pe.l + "px"; + topStyle.right = bottomStyle.right = rightWidth + rightSplitterThickness + pe.r + "px"; + }else{ + topStyle.left = bottomStyle.left = pe.l + "px"; + topStyle.right = bottomStyle.right = pe.r + "px"; + } + + // More calculations about sizes of panes + var containerHeight = this._borderBox.h - pe.t - pe.b, + middleHeight = containerHeight - ( topHeight + topSplitterThickness + bottomHeight + bottomSplitterThickness), + sidebarHeight = sidebarLayout ? containerHeight : middleHeight; + + var containerWidth = this._borderBox.w - pe.l - pe.r, + middleWidth = containerWidth - (leftWidth + leftSplitterThickness + rightWidth + rightSplitterThickness), + sidebarWidth = sidebarLayout ? middleWidth : containerWidth; + + // New margin-box size of each pane + var dim = { + top: { w: sidebarWidth, h: topHeight }, + bottom: { w: sidebarWidth, h: bottomHeight }, + left: { w: leftWidth, h: sidebarHeight }, + right: { w: rightWidth, h: sidebarHeight }, + center: { h: middleHeight, w: middleWidth } + }; + + if(changedRegion){ + // Respond to splitter drag event by changing changedRegion's width or height + var child = this["_" + changedRegion + "Widget"], + mb = {}; + mb[ /top|bottom/.test(changedRegion) ? "h" : "w"] = changedRegionSize; + child.resize ? child.resize(mb, dim[child.region]) : dojo.marginBox(child.domNode, mb); + } + + // Nodes in IE<8 don't respond to t/l/b/r, and TEXTAREA doesn't respond in any browser + var janky = dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.some(this.getChildren(), function(child){ + return child.domNode.tagName == "TEXTAREA" || child.domNode.tagName == "INPUT"; + }); + if(janky){ + // Set the size of the children the old fashioned way, by setting + // CSS width and height + + var resizeWidget = function(widget, changes, result){ + if(widget){ + (widget.resize ? widget.resize(changes, result) : dojo.marginBox(widget.domNode, changes)); + } + }; + + if(leftSplitter){ leftSplitter.style.height = sidebarHeight; } + if(rightSplitter){ rightSplitter.style.height = sidebarHeight; } + resizeWidget(this._leftWidget, {h: sidebarHeight}, dim.left); + resizeWidget(this._rightWidget, {h: sidebarHeight}, dim.right); + + if(topSplitter){ topSplitter.style.width = sidebarWidth; } + if(bottomSplitter){ bottomSplitter.style.width = sidebarWidth; } + resizeWidget(this._topWidget, {w: sidebarWidth}, dim.top); + resizeWidget(this._bottomWidget, {w: sidebarWidth}, dim.bottom); + + resizeWidget(this._centerWidget, dim.center); + }else{ + // Calculate which panes need a notification that their size has been changed + // (we've already set style.top/bottom/left/right on those other panes). + var notifySides = !changedRegion || (/top|bottom/.test(changedRegion) && this.design != "sidebar"), + notifyTopBottom = !changedRegion || (/left|right/.test(changedRegion) && this.design == "sidebar"), + notifyList = { + center: true, + left: notifySides, + right: notifySides, + top: notifyTopBottom, + bottom: notifyTopBottom + }; + + // Send notification to those panes that have changed size + dojo.forEach(this.getChildren(), function(child){ + if(child.resize && notifyList[child.region]){ + child.resize(null, dim[child.region]); + } + }, this); + } + }, + + destroy: function(){ + for(var region in this._splitters){ + var splitter = this._splitters[region]; + dijit.byNode(splitter).destroy(); + dojo.destroy(splitter); + } + delete this._splitters; + delete this._splitterThickness; + this.inherited(arguments); + } +}); + +// This argument can be specified for the children of a BorderContainer. +// Since any widget can be specified as a LayoutContainer child, mix it +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // region: [const] String + // Parameter for children of `dijit.layout.BorderContainer`. + // Values: "top", "bottom", "leading", "trailing", "left", "right", "center". + // See the `dijit.layout.BorderContainer` description for details. + region: '', + + // splitter: [const] Boolean + // Parameter for child of `dijit.layout.BorderContainer` where region != "center". + // If true, enables user to resize the widget by putting a draggable splitter between + // this widget and the region=center widget. + splitter: false, + + // minSize: [const] Number + // Parameter for children of `dijit.layout.BorderContainer`. + // Specifies a minimum size (in pixels) for this widget when resized by a splitter. + minSize: 0, + + // maxSize: [const] Number + // Parameter for children of `dijit.layout.BorderContainer`. + // Specifies a maximum size (in pixels) for this widget when resized by a splitter. + maxSize: Infinity +}); + + + +dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ], +{ + // summary: + // A draggable spacer between two items in a `dijit.layout.BorderContainer`. + // description: + // This is instantiated by `dijit.layout.BorderContainer`. Users should not + // create it directly. + // tags: + // private + +/*===== + // container: [const] dijit.layout.BorderContainer + // Pointer to the parent BorderContainer + container: null, + + // child: [const] dijit.layout._LayoutWidget + // Pointer to the pane associated with this splitter + child: null, + + // region: String + // Region of pane associated with this splitter. + // "top", "bottom", "left", "right". + region: null, +=====*/ + + // live: [const] Boolean + // If true, the child's size changes and the child widget is redrawn as you drag the splitter; + // otherwise, the size doesn't change until you drop the splitter (by mouse-up) + live: true, + + templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" waiRole="separator"><div class="dijitSplitterThumb"></div></div>', + + postCreate: function(){ + this.inherited(arguments); + this.horizontal = /top|bottom/.test(this.region); + dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V")); +// dojo.addClass(this.child.domNode, "dijitSplitterPane"); +// dojo.setSelectable(this.domNode, false); //TODO is this necessary? + + this._factor = /top|left/.test(this.region) ? 1 : -1; + + this._cookieName = this.container.id + "_" + this.region; + if(this.container.persist){ + // restore old size + var persistSize = dojo.cookie(this._cookieName); + if(persistSize){ + this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize; + } + } + }, + + _computeMaxSize: function(){ + // summary: + // Compute the maximum size that my corresponding pane can be set to + + var dim = this.horizontal ? 'h' : 'w', + thickness = this.container._splitterThickness[this.region]; + + // Get DOMNode of opposite pane, if an opposite pane exists. + // Ex: if I am the _Splitter for the left pane, then get the right pane. + var flip = {left:'right', right:'left', top:'bottom', bottom:'top', leading:'trailing', trailing:'leading'}, + oppNode = this.container["_" + flip[this.region]]; + + // I can expand up to the edge of the opposite pane, or if there's no opposite pane, then to + // edge of BorderContainer + var available = dojo.contentBox(this.container.domNode)[dim] - + (oppNode ? dojo.marginBox(oppNode)[dim] : 0) - + 20 - thickness * 2; + + return Math.min(this.child.maxSize, available); + }, + + _startDrag: function(e){ + if(!this.cover){ + this.cover = dojo.doc.createElement('div'); + dojo.addClass(this.cover, "dijitSplitterCover"); + dojo.place(this.cover, this.child.domNode, "after"); + } + dojo.addClass(this.cover, "dijitSplitterCoverActive"); + + // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up. + if(this.fake){ dojo.destroy(this.fake); } + if(!(this._resize = this.live)){ //TODO: disable live for IE6? + // create fake splitter to display at old position while we drag + (this.fake = this.domNode.cloneNode(true)).removeAttribute("id"); + dojo.addClass(this.domNode, "dijitSplitterShadow"); + dojo.place(this.fake, this.domNode, "after"); + } + dojo.addClass(this.domNode, "dijitSplitterActive"); + dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); + if(this.fake){ + dojo.removeClass(this.fake, "dijitSplitterHover"); + dojo.removeClass(this.fake, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover"); + } + + //Performance: load data info local vars for onmousevent function closure + var factor = this._factor, + max = this._computeMaxSize(), + min = this.child.minSize || 20, + isHorizontal = this.horizontal, + axis = isHorizontal ? "pageY" : "pageX", + pageStart = e[axis], + splitterStyle = this.domNode.style, + dim = isHorizontal ? 'h' : 'w', + childStart = dojo.marginBox(this.child.domNode)[dim], + region = this.region, + splitterStart = parseInt(this.domNode.style[region], 10), + resize = this._resize, + childNode = this.child.domNode, + layoutFunc = dojo.hitch(this.container, this.container._layoutChildren), + de = dojo.doc; + + this._handlers = (this._handlers || []).concat([ + dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){ + var delta = e[axis] - pageStart, + childSize = factor * delta + childStart, + boundChildSize = Math.max(Math.min(childSize, max), min); + + if(resize || forceResize){ + layoutFunc(region, boundChildSize); + } + splitterStyle[region] = factor * delta + splitterStart + (boundChildSize - childSize) + "px"; + }), + dojo.connect(de, "ondragstart", dojo.stopEvent), + dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent), + dojo.connect(de, "onmouseup", this, "_stopDrag") + ]); + dojo.stopEvent(e); + }, + + _onMouse: function(e){ + var o = (e.type == "mouseover" || e.type == "mouseenter"); + dojo.toggleClass(this.domNode, "dijitSplitterHover", o); + dojo.toggleClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o); + }, + + _stopDrag: function(e){ + try{ + if(this.cover){ + dojo.removeClass(this.cover, "dijitSplitterCoverActive"); + } + if(this.fake){ dojo.destroy(this.fake); } + dojo.removeClass(this.domNode, "dijitSplitterActive"); + dojo.removeClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); + dojo.removeClass(this.domNode, "dijitSplitterShadow"); + this._drag(e); //TODO: redundant with onmousemove? + this._drag(e, true); + }finally{ + this._cleanupHandlers(); + delete this._drag; + } + + if(this.container.persist){ + dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365}); + } + }, + + _cleanupHandlers: function(){ + dojo.forEach(this._handlers, dojo.disconnect); + delete this._handlers; + }, + + _onKeyPress: function(/*Event*/ e){ + // should we apply typematic to this? + this._resize = true; + var horizontal = this.horizontal; + var tick = 1; + var dk = dojo.keys; + switch(e.charOrCode){ + case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW: + tick *= -1; +// break; + case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW: + break; + default: +// this.inherited(arguments); + return; + } + var childSize = dojo.marginBox(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick; + this.container._layoutChildren(this.region, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize)); + dojo.stopEvent(e); + }, + + destroy: function(){ + this._cleanupHandlers(); + delete this.child; + delete this.container; + delete this.cover; + delete this.fake; + this.inherited(arguments); + } +}); + +dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated ], +{ + // summary: + // Just a spacer div to separate side pane from center pane. + // Basically a trick to lookup the gutter/splitter width from the theme. + // description: + // Instantiated by `dijit.layout.BorderContainer`. Users should not + // create directly. + // tags: + // private + + templateString: '<div class="dijitGutter" waiRole="presentation"></div>', + + postCreate: function(){ + this.horizontal = /top|bottom/.test(this.region); + dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V")); + } +}); + +} + +if(!dojo._hasResource["dijit.layout._TabContainerBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout._TabContainerBase"] = true; +dojo.provide("dijit.layout._TabContainerBase"); + + + + +dojo.declare("dijit.layout._TabContainerBase", + [dijit.layout.StackContainer, dijit._Templated], + { + // summary: + // Abstract base class for TabContainer. Must define _makeController() to instantiate + // and return the widget that displays the tab labels + // description: + // A TabContainer is a container that has multiple panes, but shows only + // one pane at a time. There are a set of tabs corresponding to each pane, + // where each tab has the name (aka title) of the pane, and optionally a close button. + + // tabPosition: String + // Defines where tabs go relative to tab content. + // "top", "bottom", "left-h", "right-h" + tabPosition: "top", + + baseClass: "dijitTabContainer", + + // tabStrip: Boolean + // Defines whether the tablist gets an extra class for layouting, putting a border/shading + // around the set of tabs. + tabStrip: false, + + // nested: Boolean + // If true, use styling for a TabContainer nested inside another TabContainer. + // For tundra etc., makes tabs look like links, and hides the outer + // border since the outer TabContainer already has a border. + nested: false, + + templateString: dojo.cache("dijit.layout", "templates/TabContainer.html", "<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" dojoAttachPoint=\"tablistNode\"></div>\n\t<div dojoAttachPoint=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" dojoAttachPoint=\"containerNode\"></div>\n</div>\n"), + + postMixInProperties: function(){ + // set class name according to tab position, ex: dijitTabContainerTop + this.baseClass += this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, ""); + + this.srcNodeRef && dojo.style(this.srcNodeRef, "visibility", "hidden"); + + this.inherited(arguments); + }, + + postCreate: function(){ + this.inherited(arguments); + + // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel + this.tablist = this._makeController(this.tablistNode); + + if(!this.doLayout){ dojo.addClass(this.domNode, "dijitTabContainerNoLayout"); } + + if(this.nested){ + /* workaround IE's lack of support for "a > b" selectors by + * tagging each node in the template. + */ + dojo.addClass(this.domNode, "dijitTabContainerNested"); + dojo.addClass(this.tablist.containerNode, "dijitTabContainerTabListNested"); + dojo.addClass(this.tablistSpacer, "dijitTabContainerSpacerNested"); + dojo.addClass(this.containerNode, "dijitTabPaneWrapperNested"); + }else{ + dojo.addClass(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled")); + } + }, + + _setupChild: function(/*dijit._Widget*/ tab){ + // Overrides StackContainer._setupChild(). + dojo.addClass(tab.domNode, "dijitTabPane"); + this.inherited(arguments); + }, + + startup: function(){ + if(this._started){ return; } + + // wire up the tablist and its tabs + this.tablist.startup(); + + this.inherited(arguments); + }, + + layout: function(){ + // Overrides StackContainer.layout(). + // Configure the content pane to take up all the space except for where the tabs are + + if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;} + + var sc = this.selectedChildWidget; + + if(this.doLayout){ + // position and size the titles and the container node + var titleAlign = this.tabPosition.replace(/-h/, ""); + this.tablist.layoutAlign = titleAlign; + var children = [this.tablist, { + domNode: this.tablistSpacer, + layoutAlign: titleAlign + }, { + domNode: this.containerNode, + layoutAlign: "client" + }]; + dijit.layout.layoutChildren(this.domNode, this._contentBox, children); + + // Compute size to make each of my children. + // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above + this._containerContentBox = dijit.layout.marginBox2contentBox(this.containerNode, children[2]); + + if(sc && sc.resize){ + sc.resize(this._containerContentBox); + } + }else{ + // just layout the tab controller, so it can position left/right buttons etc. + if(this.tablist.resize){ + this.tablist.resize({w: dojo.contentBox(this.domNode).w}); + } + + // and call resize() on the selected pane just to tell it that it's been made visible + if(sc && sc.resize){ + sc.resize(); + } + } + }, + + destroy: function(){ + if(this.tablist){ + this.tablist.destroy(); + } + this.inherited(arguments); + } +}); + + +} + +if(!dojo._hasResource["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.TabController"] = true; +dojo.provide("dijit.layout.TabController"); + + + +// Menu is used for an accessible close button, would be nice to have a lighter-weight solution + + + + + +dojo.declare("dijit.layout.TabController", + dijit.layout.StackController, +{ + // summary: + // Set of tabs (the things with titles and a close button, that you click to show a tab panel). + // Used internally by `dijit.layout.TabContainer`. + // description: + // Lets the user select the currently shown pane in a TabContainer or StackContainer. + // TabController also monitors the TabContainer, and whenever a pane is + // added or deleted updates itself accordingly. + // tags: + // private + + templateString: "<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>", + + // tabPosition: String + // Defines where tabs go relative to the content. + // "top", "bottom", "left-h", "right-h" + tabPosition: "top", + + // buttonWidget: String + // The name of the tab widget to create to correspond to each page + buttonWidget: "dijit.layout._TabButton", + + _rectifyRtlTabList: function(){ + // summary: + // For left/right TabContainer when page is RTL mode, rectify the width of all tabs to be equal, otherwise the tab widths are different in IE + + if(0 >= this.tabPosition.indexOf('-h')){ return; } + if(!this.pane2button){ return; } + + var maxWidth = 0; + for(var pane in this.pane2button){ + var ow = this.pane2button[pane].innerDiv.scrollWidth; + maxWidth = Math.max(maxWidth, ow); + } + //unify the length of all the tabs + for(pane in this.pane2button){ + this.pane2button[pane].innerDiv.style.width = maxWidth + 'px'; + } + } +}); + +dojo.declare("dijit.layout._TabButton", + dijit.layout._StackButton, + { + // summary: + // A tab (the thing you click to select a pane). + // description: + // Contains the title of the pane, and optionally a close-button to destroy the pane. + // This is an internal widget and should not be instantiated directly. + // tags: + // private + + // baseClass: String + // The CSS class applied to the domNode. + baseClass: "dijitTab", + + // Apply dijitTabCloseButtonHover when close button is hovered + cssStateNodes: { + closeNode: "dijitTabCloseButton" + }, + + templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "<div waiRole=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick'>\n <div waiRole=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div waiRole=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n \t<div waiRole=\"presentation\" dojoAttachPoint='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' />\n\t\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" dojoAttachPoint='closeNode'\n\t\t \t\tdojoAttachEvent='onclick: onClickCloseButton' waiRole=\"presentation\">\n\t\t <span dojoAttachPoint='closeText' class='dijitTabCloseText'>x</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"), + + // Override _FormWidget.scrollOnFocus. + // Don't scroll the whole tab container into view when the button is focused. + scrollOnFocus: false, + + postMixInProperties: function(){ + // Override blank iconClass from Button to do tab height adjustment on IE6, + // to make sure that tabs with and w/out close icons are same height + if(!this.iconClass){ + this.iconClass = "dijitTabButtonIcon"; + } + }, + + postCreate: function(){ + this.inherited(arguments); + dojo.setSelectable(this.containerNode, false); + + // If a custom icon class has not been set for the + // tab icon, set its width to one pixel. This ensures + // that the height styling of the tab is maintained, + // as it is based on the height of the icon. + // TODO: I still think we can just set dijitTabButtonIcon to 1px in CSS <Bill> + if(this.iconNode.className == "dijitTabButtonIcon"){ + dojo.style(this.iconNode, "width", "1px"); + } + }, + + startup: function(){ + this.inherited(arguments); + var n = this.domNode; + + // Required to give IE6 a kick, as it initially hides the + // tabs until they are focused on. + setTimeout(function(){ + n.className = n.className; + }, 1); + }, + + _setCloseButtonAttr: function(disp){ + this.closeButton = disp; + dojo.toggleClass(this.innerDiv, "dijitClosable", disp); + this.closeNode.style.display = disp ? "" : "none"; + if(disp){ + var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); + if(this.closeNode){ + dojo.attr(this.closeNode,"title", _nlsResources.itemClose); + } + // add context menu onto title button + var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); + this._closeMenu = new dijit.Menu({ + id: this.id+"_Menu", + dir: this.dir, + lang: this.lang, + targetNodeIds: [this.domNode] + }); + + this._closeMenu.addChild(new dijit.MenuItem({ + label: _nlsResources.itemClose, + dir: this.dir, + lang: this.lang, + onClick: dojo.hitch(this, "onClickCloseButton") + })); + }else{ + if(this._closeMenu){ + this._closeMenu.destroyRecursive(); + delete this._closeMenu; + } + } + }, + _setLabelAttr: function(/*String*/ content){ + // summary: + // Hook for attr('label', ...) to work. + // description: + // takes an HTML string. + // Inherited ToggleButton implementation will Set the label (text) of the button; + // Need to set the alt attribute of icon on tab buttons if no label displayed + this.inherited(arguments); + if(this.showLabel == false && !this.params.title){ + this.iconNode.alt = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || ''); + } + }, + + destroy: function(){ + if(this._closeMenu){ + this._closeMenu.destroyRecursive(); + delete this._closeMenu; + } + this.inherited(arguments); + } +}); + +} + +if(!dojo._hasResource["dijit.layout.ScrollingTabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.ScrollingTabController"] = true; +dojo.provide("dijit.layout.ScrollingTabController"); + + + + +dojo.declare("dijit.layout.ScrollingTabController", + dijit.layout.TabController, + { + // summary: + // Set of tabs with left/right arrow keys and a menu to switch between tabs not + // all fitting on a single row. + // Works only for horizontal tabs (either above or below the content, not to the left + // or right). + // tags: + // private + + templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\" iconClass=\"dijitTabStripMenuIcon\"\n\t\t\tdojoAttachPoint=\"_menuBtn\" showLabel=false>▼</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=false>◀</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=false>▶</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"), + + // useMenu:[const] Boolean + // True if a menu should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useMenu: true, + + // useSlider: [const] Boolean + // True if a slider should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useSlider: true, + + // tabStripClass: String + // The css class to apply to the tab strip, if it is visible. + tabStripClass: "", + + widgetsInTemplate: true, + + // _minScroll: Number + // The distance in pixels from the edge of the tab strip which, + // if a scroll animation is less than, forces the scroll to + // go all the way to the left/right. + _minScroll: 5, + + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + "class": "containerNode" + }), + + postCreate: function(){ + this.inherited(arguments); + var n = this.domNode; + + this.scrollNode = this.tablistWrapper; + this._initButtons(); + + if(!this.tabStripClass){ + this.tabStripClass = "dijitTabContainer" + + this.tabPosition.charAt(0).toUpperCase() + + this.tabPosition.substr(1).replace(/-.*/, "") + + "None"; + dojo.addClass(n, "tabStrip-disabled") + } + + dojo.addClass(this.tablistWrapper, this.tabStripClass); + }, + + onStartup: function(){ + this.inherited(arguments); + + // Do not show the TabController until the related + // StackController has added it's children. This gives + // a less visually jumpy instantiation. + dojo.style(this.domNode, "visibility", "visible"); + this._postStartup = true; + }, + + onAddChild: function(page, insertIndex){ + this.inherited(arguments); + var menuItem; + if(this.useMenu){ + var containerId = this.containerId; + menuItem = new dijit.MenuItem({ + id: page.id + "_stcMi", + label: page.title, + dir: page.dir, + lang: page.lang, + onClick: dojo.hitch(this, function(){ + var container = dijit.byId(containerId); + container.selectChild(page); + }) + }); + this._menuChildren[page.id] = menuItem; + this._menu.addChild(menuItem, insertIndex); + } + + // update the menuItem label when the button label is updated + this.pane2handles[page.id].push( + this.connect(this.pane2button[page.id], "set", function(name, value){ + if(this._postStartup){ + if(name == "label"){ + if(menuItem){ + menuItem.set(name, value); + } + + // The changed label will have changed the width of the + // buttons, so do a resize + if(this._dim){ + this.resize(this._dim); + } + } + } + }) + ); + + // Increment the width of the wrapper when a tab is added + // This makes sure that the buttons never wrap. + // The value 200 is chosen as it should be bigger than most + // Tab button widths. + dojo.style(this.containerNode, "width", + (dojo.style(this.containerNode, "width") + 200) + "px"); + }, + + onRemoveChild: function(page, insertIndex){ + // null out _selectedTab because we are about to delete that dom node + var button = this.pane2button[page.id]; + if(this._selectedTab === button.domNode){ + this._selectedTab = null; + } + + // delete menu entry corresponding to pane that was removed from TabContainer + if(this.useMenu && page && page.id && this._menuChildren[page.id]){ + this._menu.removeChild(this._menuChildren[page.id]); + this._menuChildren[page.id].destroy(); + delete this._menuChildren[page.id]; + } + + this.inherited(arguments); + }, + + _initButtons: function(){ + // summary: + // Creates the buttons used to scroll to view tabs that + // may not be visible if the TabContainer is too narrow. + this._menuChildren = {}; + + // Make a list of the buttons to display when the tab labels become + // wider than the TabContainer, and hide the other buttons. + // Also gets the total width of the displayed buttons. + this._btnWidth = 0; + this._buttons = dojo.query("> .tabStripButton", this.domNode).filter(function(btn){ + if((this.useMenu && btn == this._menuBtn.domNode) || + (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){ + this._btnWidth += dojo.marginBox(btn).w; + return true; + }else{ + dojo.style(btn, "display", "none"); + return false; + } + }, this); + + if(this.useMenu){ + // Create the menu that is used to select tabs. + this._menu = new dijit.Menu({ + id: this.id + "_menu", + dir: this.dir, + lang: this.lang, + targetNodeIds: [this._menuBtn.domNode], + leftClickToOpen: true, + refocus: false // selecting a menu item sets focus to a TabButton + }); + this._supportingWidgets.push(this._menu); + } + }, + + _getTabsWidth: function(){ + var children = this.getChildren(); + if(children.length){ + var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode, + rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode; + return rightTab.offsetLeft + dojo.style(rightTab, "width") - leftTab.offsetLeft; + }else{ + return 0; + } + }, + + _enableBtn: function(width){ + // summary: + // Determines if the tabs are wider than the width of the TabContainer, and + // thus that we need to display left/right/menu navigation buttons. + var tabsWidth = this._getTabsWidth(); + width = width || dojo.style(this.scrollNode, "width"); + return tabsWidth > 0 && width < tabsWidth; + }, + + resize: function(dim){ + // summary: + // Hides or displays the buttons used to scroll the tab list and launch the menu + // that selects tabs. + + if(this.domNode.offsetWidth == 0){ + return; + } + + // Save the dimensions to be used when a child is renamed. + this._dim = dim; + + // Set my height to be my natural height (tall enough for one row of tab labels), + // and my content-box width based on margin-box width specified in dim parameter. + // But first reset scrollNode.height in case it was set by layoutChildren() call + // in a previous run of this method. + this.scrollNode.style.height = "auto"; + this._contentBox = dijit.layout.marginBox2contentBox(this.domNode, {h: 0, w: dim.w}); + this._contentBox.h = this.scrollNode.offsetHeight; + dojo.contentBox(this.domNode, this._contentBox); + + // Show/hide the left/right/menu navigation buttons depending on whether or not they + // are needed. + var enable = this._enableBtn(this._contentBox.w); + this._buttons.style("display", enable ? "" : "none"); + + // Position and size the navigation buttons and the tablist + this._leftBtn.layoutAlign = "left"; + this._rightBtn.layoutAlign = "right"; + this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left"; + dijit.layout.layoutChildren(this.domNode, this._contentBox, + [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]); + + // set proper scroll so that selected tab is visible + if(this._selectedTab){ + if(this._anim && this._anim.status() == "playing"){ + this._anim.stop(); + } + var w = this.scrollNode, + sl = this._convertToScrollLeft(this._getScrollForSelectedTab()); + w.scrollLeft = sl; + } + + // Enable/disabled left right buttons depending on whether or not user can scroll to left or right + this._setButtonClass(this._getScroll()); + + this._postResize = true; + }, + + _getScroll: function(){ + // summary: + // Returns the current scroll of the tabs where 0 means + // "scrolled all the way to the left" and some positive number, based on # + // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right" + var sl = (this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit) ? this.scrollNode.scrollLeft : + dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width") + + (dojo.isIE == 8 ? -1 : 1) * this.scrollNode.scrollLeft; + return sl; + }, + + _convertToScrollLeft: function(val){ + // summary: + // Given a scroll value where 0 means "scrolled all the way to the left" + // and some positive number, based on # of pixels of possible scroll (ex: 1000) + // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft + // to achieve that scroll. + // + // This method is to adjust for RTL funniness in various browsers and versions. + if(this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit){ + return val; + }else{ + var maxScroll = dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width"); + return (dojo.isIE == 8 ? -1 : 1) * (val - maxScroll); + } + }, + + onSelectChild: function(/*dijit._Widget*/ page){ + // summary: + // Smoothly scrolls to a tab when it is selected. + + var tab = this.pane2button[page.id]; + if(!tab || !page){return;} + + // Scroll to the selected tab, except on startup, when scrolling is handled in resize() + var node = tab.domNode; + if(this._postResize && node != this._selectedTab){ + this._selectedTab = node; + + var sl = this._getScroll(); + + if(sl > node.offsetLeft || + sl + dojo.style(this.scrollNode, "width") < + node.offsetLeft + dojo.style(node, "width")){ + this.createSmoothScroll().play(); + } + } + + this.inherited(arguments); + }, + + _getScrollBounds: function(){ + // summary: + // Returns the minimum and maximum scroll setting to show the leftmost and rightmost + // tabs (respectively) + var children = this.getChildren(), + scrollNodeWidth = dojo.style(this.scrollNode, "width"), // about 500px + containerWidth = dojo.style(this.containerNode, "width"), // 50,000px + maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible + tabsWidth = this._getTabsWidth(); + + if(children.length && tabsWidth > scrollNodeWidth){ + // Scrolling should happen + return { + min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft, + max: this.isLeftToRight() ? + (children[children.length-1].domNode.offsetLeft + dojo.style(children[children.length-1].domNode, "width")) - scrollNodeWidth : + maxPossibleScroll + }; + }else{ + // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir) + var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll; + return { + min: onlyScrollPosition, + max: onlyScrollPosition + }; + } + }, + + _getScrollForSelectedTab: function(){ + // summary: + // Returns the scroll value setting so that the selected tab + // will appear in the center + var w = this.scrollNode, + n = this._selectedTab, + scrollNodeWidth = dojo.style(this.scrollNode, "width"), + scrollBounds = this._getScrollBounds(); + + // TODO: scroll minimal amount (to either right or left) so that + // selected tab is fully visible, and just return if it's already visible? + var pos = (n.offsetLeft + dojo.style(n, "width")/2) - scrollNodeWidth/2; + pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max); + + // TODO: + // If scrolling close to the left side or right side, scroll + // all the way to the left or right. See this._minScroll. + // (But need to make sure that doesn't scroll the tab out of view...) + return pos; + }, + + createSmoothScroll : function(x){ + // summary: + // Creates a dojo._Animation object that smoothly scrolls the tab list + // either to a fixed horizontal pixel value, or to the selected tab. + // description: + // If an number argument is passed to the function, that horizontal + // pixel position is scrolled to. Otherwise the currently selected + // tab is scrolled to. + // x: Integer? + // An optional pixel value to scroll to, indicating distance from left. + + // Calculate position to scroll to + if(arguments.length > 0){ + // position specified by caller, just make sure it's within bounds + var scrollBounds = this._getScrollBounds(); + x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max); + }else{ + // scroll to center the current tab + x = this._getScrollForSelectedTab(); + } + + if(this._anim && this._anim.status() == "playing"){ + this._anim.stop(); + } + + var self = this, + w = this.scrollNode, + anim = new dojo._Animation({ + beforeBegin: function(){ + if(this.curve){ delete this.curve; } + var oldS = w.scrollLeft, + newS = self._convertToScrollLeft(x); + anim.curve = new dojo._Line(oldS, newS); + }, + onAnimate: function(val){ + w.scrollLeft = val; + } + }); + this._anim = anim; + + // Disable/enable left/right buttons according to new scroll position + this._setButtonClass(x); + + return anim; // dojo._Animation + }, + + _getBtnNode: function(e){ + // summary: + // Gets a button DOM node from a mouse click event. + // e: + // The mouse click event. + var n = e.target; + while(n && !dojo.hasClass(n, "tabStripButton")){ + n = n.parentNode; + } + return n; + }, + + doSlideRight: function(e){ + // summary: + // Scrolls the menu to the right. + // e: + // The mouse click event. + this.doSlide(1, this._getBtnNode(e)); + }, + + doSlideLeft: function(e){ + // summary: + // Scrolls the menu to the left. + // e: + // The mouse click event. + this.doSlide(-1,this._getBtnNode(e)); + }, + + doSlide: function(direction, node){ + // summary: + // Scrolls the tab list to the left or right by 75% of the widget width. + // direction: + // If the direction is 1, the widget scrolls to the right, if it is + // -1, it scrolls to the left. + + if(node && dojo.hasClass(node, "dijitTabDisabled")){return;} + + var sWidth = dojo.style(this.scrollNode, "width"); + var d = (sWidth * 0.75) * direction; + + var to = this._getScroll() + d; + + this._setButtonClass(to); + + this.createSmoothScroll(to).play(); + }, + + _setButtonClass: function(scroll){ + // summary: + // Disables the left scroll button if the tabs are scrolled all the way to the left, + // or the right scroll button in the opposite case. + // scroll: Integer + // amount of horizontal scroll + + var scrollBounds = this._getScrollBounds(); + this._leftBtn.set("disabled", scroll <= scrollBounds.min); + this._rightBtn.set("disabled", scroll >= scrollBounds.max); + } +}); + +dojo.declare("dijit.layout._ScrollingTabControllerButton", + dijit.form.Button, + { + baseClass: "dijitTab tabStripButton", + + templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "<div dojoAttachEvent=\"onclick:_onButtonClick\">\n\t<div waiRole=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div waiRole=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img waiRole=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"), + + // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be + // able to tab to the left/right/menu buttons + tabIndex: "-1" + } +); + +} + +if(!dojo._hasResource["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.TabContainer"] = true; +dojo.provide("dijit.layout.TabContainer"); + + + + + +dojo.declare("dijit.layout.TabContainer", + dijit.layout._TabContainerBase, + { + // summary: + // A Container with tabs to select each child (only one of which is displayed at a time). + // description: + // A TabContainer is a container that has multiple panes, but shows only + // one pane at a time. There are a set of tabs corresponding to each pane, + // where each tab has the name (aka title) of the pane, and optionally a close button. + + // useMenu: [const] Boolean + // True if a menu should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useMenu: true, + + // useSlider: [const] Boolean + // True if a slider should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useSlider: true, + + // controllerWidget: String + // An optional parameter to override the widget used to display the tab labels + controllerWidget: "", + + _makeController: function(/*DomNode*/ srcNode){ + // summary: + // Instantiate tablist controller widget and return reference to it. + // Callback from _TabContainerBase.postCreate(). + // tags: + // protected extension + + var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"), + TabController = dojo.getObject(this.controllerWidget); + + return new TabController({ + id: this.id + "_tablist", + dir: this.dir, + lang: this.lang, + tabPosition: this.tabPosition, + doLayout: this.doLayout, + containerId: this.id, + "class": cls, + nested: this.nested, + useMenu: this.useMenu, + useSlider: this.useSlider, + tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null + }, srcNode); + }, + + postMixInProperties: function(){ + this.inherited(arguments); + + // Scrolling controller only works for horizontal non-nested tabs + if(!this.controllerWidget){ + this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ? + "dijit.layout.ScrollingTabController" : "dijit.layout.TabController"; + } + } +}); + + +} + +if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.number"] = true; +dojo.provide("dojo.number"); + + + + + + + +/*===== +dojo.number = { + // summary: localized formatting and parsing routines for Number +} + +dojo.number.__FormatOptions = function(){ + // pattern: String? + // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // with this string. Default value is based on locale. Overriding this property will defeat + // localization. Literal characters in patterns are not supported. + // type: String? + // choose a format type based on the locale from the following: + // decimal, scientific (not yet supported), percent, currency. decimal by default. + // places: Number? + // fixed number of decimal places to show. This overrides any + // information in the provided pattern. + // round: Number? + // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1 + // means do not round. + // locale: String? + // override the locale used to determine formatting rules + // fractional: Boolean? + // If false, show no decimal places, overriding places and pattern settings. + this.pattern = pattern; + this.type = type; + this.places = places; + this.round = round; + this.locale = locale; + this.fractional = fractional; +} +=====*/ + +dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){ + // summary: + // Format a Number as a String, using locale-specific settings + // description: + // Create a string from a Number using a known localized pattern. + // Formatting patterns appropriate to the locale are chosen from the + // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and + // delimiters. + // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null. + // value: + // the number to be formatted + + options = dojo.mixin({}, options || {}); + var locale = dojo.i18n.normalizeLocale(options.locale), + bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale); + options.customs = bundle; + var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"]; + if(isNaN(value) || Math.abs(value) == Infinity){ return null; } // null + return dojo.number._applyPattern(value, pattern, options); // String +}; + +//dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough +dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough + +dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){ + // summary: + // Apply pattern to format value as a string using options. Gives no + // consideration to local customs. + // value: + // the number to be formatted. + // pattern: + // a pattern string as described by + // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // options: dojo.number.__FormatOptions? + // _applyPattern is usually called via `dojo.number.format()` which + // populates an extra property in the options parameter, "customs". + // The customs object specifies group and decimal parameters if set. + + //TODO: support escapes + options = options || {}; + var group = options.customs.group, + decimal = options.customs.decimal, + patternList = pattern.split(';'), + positivePattern = patternList[0]; + pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern); + + //TODO: only test against unescaped + if(pattern.indexOf('%') != -1){ + value *= 100; + }else if(pattern.indexOf('\u2030') != -1){ + value *= 1000; // per mille + }else if(pattern.indexOf('\u00a4') != -1){ + group = options.customs.currencyGroup || group;//mixins instead? + decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead? + pattern = pattern.replace(/\u00a4{1,3}/, function(match){ + var prop = ["symbol", "currency", "displayName"][match.length-1]; + return options[prop] || options.currency || ""; + }); + }else if(pattern.indexOf('E') != -1){ + throw new Error("exponential notation not supported"); + } + + //TODO: support @ sig figs? + var numberPatternRE = dojo.number._numberPatternRE; + var numberPattern = positivePattern.match(numberPatternRE); + if(!numberPattern){ + throw new Error("unable to find a number expression in pattern: "+pattern); + } + if(options.fractional === false){ options.places = 0; } + return pattern.replace(numberPatternRE, + dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round})); +} + +dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){ + // summary: + // Rounds to the nearest value with the given number of decimal places, away from zero + // description: + // Rounds to the nearest value with the given number of decimal places, away from zero if equal. + // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by + // fractional increments also, such as the nearest quarter. + // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround. + // value: + // The number to round + // places: + // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding. + // Must be non-negative. + // increment: + // Rounds next place to nearest value of increment/10. 10 by default. + // example: + // >>> dojo.number.round(-0.5) + // -1 + // >>> dojo.number.round(162.295, 2) + // 162.29 // note floating point error. Should be 162.3 + // >>> dojo.number.round(10.71, 0, 2.5) + // 10.75 + var factor = 10 / (increment || 10); + return (factor * +value).toFixed(places) / factor; // Number +} + +if((0.9).toFixed() == 0){ + // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit + // is just after the rounding place and is >=5 + (function(){ + var round = dojo.number.round; + dojo.number.round = function(v, p, m){ + var d = Math.pow(10, -p || 0), a = Math.abs(v); + if(!v || a >= d || a * Math.pow(10, p + 1) < 5){ + d = 0; + } + return round(v, p, m) + (v > 0 ? d : -d); + } + })(); +} + +/*===== +dojo.number.__FormatAbsoluteOptions = function(){ + // decimal: String? + // the decimal separator + // group: String? + // the group separator + // places: Number?|String? + // number of decimal places. the range "n,m" will format to m places. + // round: Number? + // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1 + // means don't round. + this.decimal = decimal; + this.group = group; + this.places = places; + this.round = round; +} +=====*/ + +dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){ + // summary: + // Apply numeric pattern to absolute value using options. Gives no + // consideration to local customs. + // value: + // the number to be formatted, ignores sign + // pattern: + // the number portion of a pattern (e.g. `#,##0.00`) + options = options || {}; + if(options.places === true){options.places=0;} + if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit + + var patternParts = pattern.split("."), + comma = typeof options.places == "string" && options.places.indexOf(","), + maxPlaces = options.places; + if(comma){ + maxPlaces = options.places.substring(comma + 1); + }else if(!(maxPlaces >= 0)){ + maxPlaces = (patternParts[1] || []).length; + } + if(!(options.round < 0)){ + value = dojo.number.round(value, maxPlaces, options.round); + } + + var valueParts = String(Math.abs(value)).split("."), + fractional = valueParts[1] || ""; + if(patternParts[1] || options.places){ + if(comma){ + options.places = options.places.substring(0, comma); + } + // Pad fractional with trailing zeros + var pad = options.places !== undefined ? options.places : (patternParts[1] && patternParts[1].lastIndexOf("0") + 1); + if(pad > fractional.length){ + valueParts[1] = dojo.string.pad(fractional, pad, '0', true); + } + + // Truncate fractional + if(maxPlaces < fractional.length){ + valueParts[1] = fractional.substr(0, maxPlaces); + } + }else{ + if(valueParts[1]){ valueParts.pop(); } + } + + // Pad whole with leading zeros + var patternDigits = patternParts[0].replace(',', ''); + pad = patternDigits.indexOf("0"); + if(pad != -1){ + pad = patternDigits.length - pad; + if(pad > valueParts[0].length){ + valueParts[0] = dojo.string.pad(valueParts[0], pad); + } + + // Truncate whole + if(patternDigits.indexOf("#") == -1){ + valueParts[0] = valueParts[0].substr(valueParts[0].length - pad); + } + } + + // Add group separators + var index = patternParts[0].lastIndexOf(','), + groupSize, groupSize2; + if(index != -1){ + groupSize = patternParts[0].length - index - 1; + var remainder = patternParts[0].substr(0, index); + index = remainder.lastIndexOf(','); + if(index != -1){ + groupSize2 = remainder.length - index - 1; + } + } + var pieces = []; + for(var whole = valueParts[0]; whole;){ + var off = whole.length - groupSize; + pieces.push((off > 0) ? whole.substr(off) : whole); + whole = (off > 0) ? whole.slice(0, off) : ""; + if(groupSize2){ + groupSize = groupSize2; + delete groupSize2; + } + } + valueParts[0] = pieces.reverse().join(options.group || ","); + + return valueParts.join(options.decimal || "."); +}; + +/*===== +dojo.number.__RegexpOptions = function(){ + // pattern: String? + // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // with this string. Default value is based on locale. Overriding this property will defeat + // localization. + // type: String? + // choose a format type based on the locale from the following: + // decimal, scientific (not yet supported), percent, currency. decimal by default. + // locale: String? + // override the locale used to determine formatting rules + // strict: Boolean? + // strict parsing, false by default. Strict parsing requires input as produced by the format() method. + // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators + // places: Number|String? + // number of decimal places to accept: Infinity, a positive number, or + // a range "n,m". Defined by pattern or Infinity if pattern not provided. + this.pattern = pattern; + this.type = type; + this.locale = locale; + this.strict = strict; + this.places = places; +} +=====*/ +dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){ + // summary: + // Builds the regular needed to parse a number + // description: + // Returns regular expression with positive and negative match, group + // and decimal separators + return dojo.number._parseInfo(options).regexp; // String +} + +dojo.number._parseInfo = function(/*Object?*/options){ + options = options || {}; + var locale = dojo.i18n.normalizeLocale(options.locale), + bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale), + pattern = options.pattern || bundle[(options.type || "decimal") + "Format"], +//TODO: memoize? + group = bundle.group, + decimal = bundle.decimal, + factor = 1; + + if(pattern.indexOf('%') != -1){ + factor /= 100; + }else if(pattern.indexOf('\u2030') != -1){ + factor /= 1000; // per mille + }else{ + var isCurrency = pattern.indexOf('\u00a4') != -1; + if(isCurrency){ + group = bundle.currencyGroup || group; + decimal = bundle.currencyDecimal || decimal; + } + } + + //TODO: handle quoted escapes + var patternList = pattern.split(';'); + if(patternList.length == 1){ + patternList.push("-" + patternList[0]); + } + + var re = dojo.regexp.buildGroupRE(patternList, function(pattern){ + pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")"; + return pattern.replace(dojo.number._numberPatternRE, function(format){ + var flags = { + signed: false, + separator: options.strict ? group : [group,""], + fractional: options.fractional, + decimal: decimal, + exponent: false + }, + + parts = format.split('.'), + places = options.places; + + // special condition for percent (factor != 1) + // allow decimal places even if not specified in pattern + if(parts.length == 1 && factor != 1){ + parts[1] = "###"; + } + if(parts.length == 1 || places === 0){ + flags.fractional = false; + }else{ + if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0') + 1 : Infinity; } + if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified + if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; } + flags.places = places; + } + var groups = parts[0].split(','); + if(groups.length > 1){ + flags.groupSize = groups.pop().length; + if(groups.length > 1){ + flags.groupSize2 = groups.pop().length; + } + } + return "("+dojo.number._realNumberRegexp(flags)+")"; + }); + }, true); + + if(isCurrency){ + // substitute the currency symbol for the placeholder in the pattern + re = re.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){ + var prop = ["symbol", "currency", "displayName"][target.length-1], + symbol = dojo.regexp.escapeString(options[prop] || options.currency || ""); + before = before ? "[\\s\\xa0]" : ""; + after = after ? "[\\s\\xa0]" : ""; + if(!options.strict){ + if(before){before += "*";} + if(after){after += "*";} + return "(?:"+before+symbol+after+")?"; + } + return before+symbol+after; + }); + } + +//TODO: substitute localized sign/percent/permille/etc.? + + // normalize whitespace and return + return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object +} + +/*===== +dojo.number.__ParseOptions = function(){ + // pattern: String? + // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // with this string. Default value is based on locale. Overriding this property will defeat + // localization. Literal characters in patterns are not supported. + // type: String? + // choose a format type based on the locale from the following: + // decimal, scientific (not yet supported), percent, currency. decimal by default. + // locale: String? + // override the locale used to determine formatting rules + // strict: Boolean? + // strict parsing, false by default. Strict parsing requires input as produced by the format() method. + // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators + // fractional: Boolean?|Array? + // Whether to include the fractional portion, where the number of decimal places are implied by pattern + // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional. + this.pattern = pattern; + this.type = type; + this.locale = locale; + this.strict = strict; + this.fractional = fractional; +} +=====*/ +dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){ + // summary: + // Convert a properly formatted string to a primitive Number, using + // locale-specific settings. + // description: + // Create a Number from a string using a known localized pattern. + // Formatting patterns are chosen appropriate to the locale + // and follow the syntax described by + // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // Note that literal characters in patterns are not supported. + // expression: + // A string representation of a Number + var info = dojo.number._parseInfo(options), + results = (new RegExp("^"+info.regexp+"$")).exec(expression); + if(!results){ + return NaN; //NaN + } + var absoluteMatch = results[1]; // match for the positive expression + if(!results[1]){ + if(!results[2]){ + return NaN; //NaN + } + // matched the negative pattern + absoluteMatch =results[2]; + info.factor *= -1; + } + + // Transform it to something Javascript can parse as a number. Normalize + // decimal point and strip out group separators or alternate forms of whitespace + absoluteMatch = absoluteMatch. + replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), ""). + replace(info.decimal, "."); + // Adjust for negative sign, percent, etc. as necessary + return absoluteMatch * info.factor; //Number +}; + +/*===== +dojo.number.__RealNumberRegexpFlags = function(){ + // places: Number? + // The integer number of decimal places or a range given as "n,m". If + // not given, the decimal part is optional and the number of places is + // unlimited. + // decimal: String? + // A string for the character used as the decimal point. Default + // is ".". + // fractional: Boolean?|Array? + // Whether decimal places are used. Can be true, false, or [true, + // false]. Default is [true, false] which means optional. + // exponent: Boolean?|Array? + // Express in exponential notation. Can be true, false, or [true, + // false]. Default is [true, false], (i.e. will match if the + // exponential part is present are not). + // eSigned: Boolean?|Array? + // The leading plus-or-minus sign on the exponent. Can be true, + // false, or [true, false]. Default is [true, false], (i.e. will + // match if it is signed or unsigned). flags in regexp.integer can be + // applied. + this.places = places; + this.decimal = decimal; + this.fractional = fractional; + this.exponent = exponent; + this.eSigned = eSigned; +} +=====*/ + +dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){ + // summary: + // Builds a regular expression to match a real number in exponential + // notation + + // assign default values to missing parameters + flags = flags || {}; + //TODO: use mixin instead? + if(!("places" in flags)){ flags.places = Infinity; } + if(typeof flags.decimal != "string"){ flags.decimal = "."; } + if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; } + if(!("exponent" in flags)){ flags.exponent = [true, false]; } + if(!("eSigned" in flags)){ flags.eSigned = [true, false]; } + + var integerRE = dojo.number._integerRegexp(flags), + decimalRE = dojo.regexp.buildGroupRE(flags.fractional, + function(q){ + var re = ""; + if(q && (flags.places!==0)){ + re = "\\" + flags.decimal; + if(flags.places == Infinity){ + re = "(?:" + re + "\\d+)?"; + }else{ + re += "\\d{" + flags.places + "}"; + } + } + return re; + }, + true + ); + + var exponentRE = dojo.regexp.buildGroupRE(flags.exponent, + function(q){ + if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; } + return ""; + } + ); + + var realRE = integerRE + decimalRE; + // allow for decimals without integers, e.g. .25 + if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";} + return realRE + exponentRE; // String +}; + +/*===== +dojo.number.__IntegerRegexpFlags = function(){ + // signed: Boolean? + // The leading plus-or-minus sign. Can be true, false, or `[true,false]`. + // Default is `[true, false]`, (i.e. will match if it is signed + // or unsigned). + // separator: String? + // The character used as the thousands separator. Default is no + // separator. For more than one symbol use an array, e.g. `[",", ""]`, + // makes ',' optional. + // groupSize: Number? + // group size between separators + // groupSize2: Number? + // second grouping, where separators 2..n have a different interval than the first separator (for India) + this.signed = signed; + this.separator = separator; + this.groupSize = groupSize; + this.groupSize2 = groupSize2; +} +=====*/ + +dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){ + // summary: + // Builds a regular expression that matches an integer + + // assign default values to missing parameters + flags = flags || {}; + if(!("signed" in flags)){ flags.signed = [true, false]; } + if(!("separator" in flags)){ + flags.separator = ""; + }else if(!("groupSize" in flags)){ + flags.groupSize = 3; + } + + var signRE = dojo.regexp.buildGroupRE(flags.signed, + function(q){ return q ? "[-+]" : ""; }, + true + ); + + var numberRE = dojo.regexp.buildGroupRE(flags.separator, + function(sep){ + if(!sep){ + return "(?:\\d+)"; + } + + sep = dojo.regexp.escapeString(sep); + if(sep == " "){ sep = "\\s"; } + else if(sep == "\xa0"){ sep = "\\s\\xa0"; } + + var grp = flags.groupSize, grp2 = flags.groupSize2; + //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933 + if(grp2){ + var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})"; + return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE; + } + return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)"; + }, + true + ); + + return signRE + numberRE; // String +} + +} + +if(!dojo._hasResource["dijit.ProgressBar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.ProgressBar"] = true; +dojo.provide("dijit.ProgressBar"); + + + + + + + +dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], { + // summary: + // A progress indication widget, showing the amount completed + // (often the percentage completed) of a task. + // + // example: + // | <div dojoType="ProgressBar" + // | places="0" + // | progress="..." maximum="..."> + // | </div> + // + // description: + // Note that the progress bar is updated via (a non-standard) + // update() method, rather than via attr() like other widgets. + + // progress: [const] String (Percentage or Number) + // Number or percentage indicating amount of task completed. + // With "%": percentage value, 0% <= progress <= 100%, or + // without "%": absolute value, 0 <= progress <= maximum + // TODO: rename to value for 2.0 + progress: "0", + + // maximum: [const] Float + // Max sample number + maximum: 100, + + // places: [const] Number + // Number of places to show in values; 0 by default + places: 0, + + // indeterminate: [const] Boolean + // If false: show progress value (number or percentage). + // If true: show that a process is underway but that the amount completed is unknown. + indeterminate: false, + + // name: String + // this is the field name (for a form) if set. This needs to be set if you want to use + // this widget in a dijit.form.Form widget (such as dijit.Dialog) + name: '', + + templateString: dojo.cache("dijit", "templates/ProgressBar.html", "<div class=\"dijitProgressBar dijitProgressBarEmpty\"\n\t><div waiRole=\"progressbar\" dojoAttachPoint=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\"></div\n\t\t><span style=\"visibility:hidden\"> </span\n\t></div\n\t><div dojoAttachPoint=\"label\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"> </div\n\t><img dojoAttachPoint=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"), + + // _indeterminateHighContrastImagePath: [private] dojo._URL + // URL to image to use for indeterminate progress bar when display is in high contrast mode + _indeterminateHighContrastImagePath: + dojo.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"), + + // public functions + postCreate: function(){ + this.inherited(arguments); + this.indeterminateHighContrastImage.setAttribute("src", + this._indeterminateHighContrastImagePath.toString()); + this.update(); + }, + + update: function(/*Object?*/attributes){ + // summary: + // Change attributes of ProgressBar, similar to attr(hash). + // + // attributes: + // May provide progress and/or maximum properties on this parameter; + // see attribute specs for details. + // + // example: + // | myProgressBar.update({'indeterminate': true}); + // | myProgressBar.update({'progress': 80}); + + // TODO: deprecate this method and use set() instead + + dojo.mixin(this, attributes || {}); + var tip = this.internalProgress; + var percent = 1, classFunc; + if(this.indeterminate){ + classFunc = "addClass"; + dijit.removeWaiState(tip, "valuenow"); + dijit.removeWaiState(tip, "valuemin"); + dijit.removeWaiState(tip, "valuemax"); + }else{ + classFunc = "removeClass"; + if(String(this.progress).indexOf("%") != -1){ + percent = Math.min(parseFloat(this.progress)/100, 1); + this.progress = percent * this.maximum; + }else{ + this.progress = Math.min(this.progress, this.maximum); + percent = this.progress / this.maximum; + } + var text = this.report(percent); + this.label.firstChild.nodeValue = text; + dijit.setWaiState(tip, "describedby", this.label.id); + dijit.setWaiState(tip, "valuenow", this.progress); + dijit.setWaiState(tip, "valuemin", 0); + dijit.setWaiState(tip, "valuemax", this.maximum); + } + dojo[classFunc](this.domNode, "dijitProgressBarIndeterminate"); + tip.style.width = (percent * 100) + "%"; + this.onChange(); + }, + + _setValueAttr: function(v){ + if(v == Infinity){ + this.update({indeterminate:true}); + }else{ + this.update({indeterminate:false, progress:v}); + } + }, + + _getValueAttr: function(){ + return this.progress; + }, + + report: function(/*float*/percent){ + // summary: + // Generates message to show inside progress bar (normally indicating amount of task completed). + // May be overridden. + // tags: + // extension + + return dojo.number.format(percent, { type: "percent", places: this.places, locale: this.lang }); + }, + + onChange: function(){ + // summary: + // Callback fired when progress updates. + // tags: + // progress + } +}); + +} + +if(!dojo._hasResource["dijit.ToolbarSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.ToolbarSeparator"] = true; +dojo.provide("dijit.ToolbarSeparator"); + + + + +dojo.declare("dijit.ToolbarSeparator", + [ dijit._Widget, dijit._Templated ], + { + // summary: + // A spacer between two `dijit.Toolbar` items + templateString: '<div class="dijitToolbarSeparator dijitInline" waiRole="presentation"></div>', + postCreate: function(){ dojo.setSelectable(this.domNode, false); }, + isFocusable: function(){ + // summary: + // This widget isn't focusable, so pass along that fact. + // tags: + // protected + return false; + } + + }); + + + +} + +if(!dojo._hasResource["dijit.Toolbar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.Toolbar"] = true; +dojo.provide("dijit.Toolbar"); + + + + + +dojo.declare("dijit.Toolbar", + [dijit._Widget, dijit._Templated, dijit._KeyNavContainer], + { + // summary: + // A Toolbar widget, used to hold things like `dijit.Editor` buttons + + templateString: + '<div class="dijit" waiRole="toolbar" tabIndex="${tabIndex}" dojoAttachPoint="containerNode">' + + // '<table style="table-layout: fixed" class="dijitReset dijitToolbarTable">' + // factor out style + // '<tr class="dijitReset" dojoAttachPoint="containerNode"></tr>'+ + // '</table>' + + '</div>', + + baseClass: "dijitToolbar", + + postCreate: function(){ + this.connectKeyNavHandlers( + this.isLeftToRight() ? [dojo.keys.LEFT_ARROW] : [dojo.keys.RIGHT_ARROW], + this.isLeftToRight() ? [dojo.keys.RIGHT_ARROW] : [dojo.keys.LEFT_ARROW] + ); + this.inherited(arguments); + }, + + startup: function(){ + if(this._started){ return; } + + this.startupKeyNavChildren(); + + this.inherited(arguments); + } +} +); + +// For back-compat, remove for 2.0 + + +} + +if(!dojo._hasResource["dojo.DeferredList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.DeferredList"] = true; +dojo.provide("dojo.DeferredList"); +dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){ + // summary: + // Provides event handling for a group of Deferred objects. + // description: + // DeferredList takes an array of existing deferreds and returns a new deferred of its own + // this new deferred will typically have its callback fired when all of the deferreds in + // the given list have fired their own deferreds. The parameters `fireOnOneCallback` and + // fireOnOneErrback, will fire before all the deferreds as appropriate + // + // list: + // The list of deferreds to be synchronizied with this DeferredList + // fireOnOneCallback: + // Will cause the DeferredLists callback to be fired as soon as any + // of the deferreds in its list have been fired instead of waiting until + // the entire list has finished + // fireonOneErrback: + // Will cause the errback to fire upon any of the deferreds errback + // canceller: + // A deferred canceller function, see dojo.Deferred + var resultList = []; + dojo.Deferred.call(this); + var self = this; + if(list.length === 0 && !fireOnOneCallback){ + this.resolve([0, []]); + } + var finished = 0; + dojo.forEach(list, function(item, i){ + item.then(function(result){ + if(fireOnOneCallback){ + self.resolve([i, result]); + }else{ + addResult(true, result); + } + },function(error){ + if(fireOnOneErrback){ + self.reject(error); + }else{ + addResult(false, error); + } + if(consumeErrors){ + return null; + } + throw error; + }); + function addResult(succeeded, result){ + resultList[i] = [succeeded, result]; + finished++; + if(finished === list.length){ + self.resolve(resultList); + } + + } + }); +}; +dojo.DeferredList.prototype = new dojo.Deferred(); + +dojo.DeferredList.prototype.gatherResults= function(deferredList){ + // summary: + // Gathers the results of the deferreds for packaging + // as the parameters to the Deferred Lists' callback + + var d = new dojo.DeferredList(deferredList, false, true, false); + d.addCallback(function(results){ + var ret = []; + dojo.forEach(results, function(result){ + ret.push(result[1]); + }); + return ret; + }); + return d; +}; + +} + +if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.tree.TreeStoreModel"] = true; +dojo.provide("dijit.tree.TreeStoreModel"); + +dojo.declare( + "dijit.tree.TreeStoreModel", + null, + { + // summary: + // Implements dijit.Tree.model connecting to a store with a single + // root item. Any methods passed into the constructor will override + // the ones defined here. + + // store: dojo.data.Store + // Underlying store + store: null, + + // childrenAttrs: String[] + // One or more attribute names (attributes in the dojo.data item) that specify that item's children + childrenAttrs: ["children"], + + // newItemIdAttr: String + // Name of attribute in the Object passed to newItem() that specifies the id. + // + // If newItemIdAttr is set then it's used when newItem() is called to see if an + // item with the same id already exists, and if so just links to the old item + // (so that the old item ends up with two parents). + // + // Setting this to null or "" will make every drop create a new item. + newItemIdAttr: "id", + + // labelAttr: String + // If specified, get label for tree node from this attribute, rather + // than by calling store.getLabel() + labelAttr: "", + + // root: [readonly] dojo.data.Item + // Pointer to the root item (read only, not a parameter) + root: null, + + // query: anything + // Specifies datastore query to return the root item for the tree. + // Must only return a single item. Alternately can just pass in pointer + // to root item. + // example: + // | {id:'ROOT'} + query: null, + + // deferItemLoadingUntilExpand: Boolean + // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes + // until they are expanded. This allows for lazying loading where only one + // loadItem (and generally one network call, consequently) per expansion + // (rather than one for each child). + // This relies on partial loading of the children items; each children item of a + // fully loaded item should contain the label and info about having children. + deferItemLoadingUntilExpand: false, + + constructor: function(/* Object */ args){ + // summary: + // Passed the arguments listed above (store, etc) + // tags: + // private + + dojo.mixin(this, args); + + this.connects = []; + + var store = this.store; + if(!store.getFeatures()['dojo.data.api.Identity']){ + throw new Error("dijit.Tree: store must support dojo.data.Identity"); + } + + // if the store supports Notification, subscribe to the notification events + if(store.getFeatures()['dojo.data.api.Notification']){ + this.connects = this.connects.concat([ + dojo.connect(store, "onNew", this, "onNewItem"), + dojo.connect(store, "onDelete", this, "onDeleteItem"), + dojo.connect(store, "onSet", this, "onSetItem") + ]); + } + }, + + destroy: function(){ + dojo.forEach(this.connects, dojo.disconnect); + // TODO: should cancel any in-progress processing of getRoot(), getChildren() + }, + + // ======================================================================= + // Methods for traversing hierarchy + + getRoot: function(onItem, onError){ + // summary: + // Calls onItem with the root item for the tree, possibly a fabricated item. + // Calls onError on error. + if(this.root){ + onItem(this.root); + }else{ + this.store.fetch({ + query: this.query, + onComplete: dojo.hitch(this, function(items){ + if(items.length != 1){ + throw new Error(this.declaredClass + ": query " + dojo.toJson(this.query) + " returned " + items.length + + " items, but must return exactly one item"); + } + this.root = items[0]; + onItem(this.root); + }), + onError: onError + }); + } + }, + + mayHaveChildren: function(/*dojo.data.Item*/ item){ + // summary: + // Tells if an item has or may have children. Implementing logic here + // avoids showing +/- expando icon for nodes that we know don't have children. + // (For efficiency reasons we may not want to check if an element actually + // has children until user clicks the expando node) + return dojo.some(this.childrenAttrs, function(attr){ + return this.store.hasAttribute(item, attr); + }, this); + }, + + getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ + // summary: + // Calls onComplete() with array of child items of given parent item, all loaded. + + var store = this.store; + if(!store.isItemLoaded(parentItem)){ + // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand + // mode, so we will load it and just return the children (without loading each + // child item) + var getChildren = dojo.hitch(this, arguments.callee); + store.loadItem({ + item: parentItem, + onItem: function(parentItem){ + getChildren(parentItem, onComplete, onError); + }, + onError: onError + }); + return; + } + // get children of specified item + var childItems = []; + for(var i=0; i<this.childrenAttrs.length; i++){ + var vals = store.getValues(parentItem, this.childrenAttrs[i]); + childItems = childItems.concat(vals); + } + + // count how many items need to be loaded + var _waitCount = 0; + if(!this.deferItemLoadingUntilExpand){ + dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } }); + } + + if(_waitCount == 0){ + // all items are already loaded (or we aren't loading them). proceed... + onComplete(childItems); + }else{ + // still waiting for some or all of the items to load + dojo.forEach(childItems, function(item, idx){ + if(!store.isItemLoaded(item)){ + store.loadItem({ + item: item, + onItem: function(item){ + childItems[idx] = item; + if(--_waitCount == 0){ + // all nodes have been loaded, send them to the tree + onComplete(childItems); + } + }, + onError: onError + }); + } + }); + } + }, + + // ======================================================================= + // Inspecting items + + isItem: function(/* anything */ something){ + return this.store.isItem(something); // Boolean + }, + + fetchItemByIdentity: function(/* object */ keywordArgs){ + this.store.fetchItemByIdentity(keywordArgs); + }, + + getIdentity: function(/* item */ item){ + return this.store.getIdentity(item); // Object + }, + + getLabel: function(/*dojo.data.Item*/ item){ + // summary: + // Get the label for an item + if(this.labelAttr){ + return this.store.getValue(item,this.labelAttr); // String + }else{ + return this.store.getLabel(item); // String + } + }, + + // ======================================================================= + // Write interface + + newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ + // summary: + // Creates a new item. See `dojo.data.api.Write` for details on args. + // Used in drag & drop when item from external source dropped onto tree. + // description: + // Developers will need to override this method if new items get added + // to parents with multiple children attributes, in order to define which + // children attribute points to the new item. + + var pInfo = {parent: parent, attribute: this.childrenAttrs[0], insertIndex: insertIndex}; + + if(this.newItemIdAttr && args[this.newItemIdAttr]){ + // Maybe there's already a corresponding item in the store; if so, reuse it. + this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){ + if(item){ + // There's already a matching item in store, use it + this.pasteItem(item, null, parent, true, insertIndex); + }else{ + // Create new item in the tree, based on the drag source. + this.store.newItem(args, pInfo); + } + }}); + }else{ + // [as far as we know] there is no id so we must assume this is a new item + this.store.newItem(args, pInfo); + } + }, + + pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ + // summary: + // Move or copy an item from one parent item to another. + // Used in drag & drop + var store = this.store, + parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item + + // remove child from source item, and record the attribute that child occurred in + if(oldParentItem){ + dojo.forEach(this.childrenAttrs, function(attr){ + if(store.containsValue(oldParentItem, attr, childItem)){ + if(!bCopy){ + var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){ + return x != childItem; + }); + store.setValues(oldParentItem, attr, values); + } + parentAttr = attr; + } + }); + } + + // modify target item's children attribute to include this item + if(newParentItem){ + if(typeof insertIndex == "number"){ + // call slice() to avoid modifying the original array, confusing the data store + var childItems = store.getValues(newParentItem, parentAttr).slice(); + childItems.splice(insertIndex, 0, childItem); + store.setValues(newParentItem, parentAttr, childItems); + }else{ + store.setValues(newParentItem, parentAttr, + store.getValues(newParentItem, parentAttr).concat(childItem)); + } + } + }, + + // ======================================================================= + // Callbacks + + onChange: function(/*dojo.data.Item*/ item){ + // summary: + // Callback whenever an item has changed, so that Tree + // can update the label, icon, etc. Note that changes + // to an item's children or parent(s) will trigger an + // onChildrenChange() so you can ignore those changes here. + // tags: + // callback + }, + + onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ + // summary: + // Callback to do notifications about new, updated, or deleted items. + // tags: + // callback + }, + + onDelete: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ + // summary: + // Callback when an item has been deleted. + // description: + // Note that there will also be an onChildrenChange() callback for the parent + // of this item. + // tags: + // callback + }, + + // ======================================================================= + // Events from data store + + onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ + // summary: + // Handler for when new items appear in the store, either from a drop operation + // or some other way. Updates the tree view (if necessary). + // description: + // If the new item is a child of an existing item, + // calls onChildrenChange() with the new list of children + // for that existing item. + // + // tags: + // extension + + // We only care about the new item if it has a parent that corresponds to a TreeNode + // we are currently displaying + if(!parentInfo){ + return; + } + + // Call onChildrenChange() on parent (ie, existing) item with new list of children + // In the common case, the new list of children is simply parentInfo.newValue or + // [ parentInfo.newValue ], although if items in the store has multiple + // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue, + // so call getChildren() to be sure to get right answer. + this.getChildren(parentInfo.item, dojo.hitch(this, function(children){ + this.onChildrenChange(parentInfo.item, children); + })); + }, + + onDeleteItem: function(/*Object*/ item){ + // summary: + // Handler for delete notifications from underlying store + this.onDelete(item); + }, + + onSetItem: function(/* item */ item, + /* attribute-name-string */ attribute, + /* object | array */ oldValue, + /* object | array */ newValue){ + // summary: + // Updates the tree view according to changes in the data store. + // description: + // Handles updates to an item's children by calling onChildrenChange(), and + // other updates to an item by calling onChange(). + // + // See `onNewItem` for more details on handling updates to an item's children. + // tags: + // extension + + if(dojo.indexOf(this.childrenAttrs, attribute) != -1){ + // item's children list changed + this.getChildren(item, dojo.hitch(this, function(children){ + // See comments in onNewItem() about calling getChildren() + this.onChildrenChange(item, children); + })); + }else{ + // item's label/icon/etc. changed. + this.onChange(item); + } + } + }); + + + +} + +if(!dojo._hasResource["dijit.tree.ForestStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.tree.ForestStoreModel"] = true; +dojo.provide("dijit.tree.ForestStoreModel"); + + + +dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, { + // summary: + // Interface between Tree and a dojo.store that doesn't have a root item, + // i.e. has multiple "top level" items. + // + // description + // Use this class to wrap a dojo.store, making all the items matching the specified query + // appear as children of a fabricated "root item". If no query is specified then all the + // items returned by fetch() on the underlying store become children of the root item. + // It allows dijit.Tree to assume a single root item, even if the store doesn't have one. + + // Parameters to constructor + + // rootId: String + // ID of fabricated root item + rootId: "$root$", + + // rootLabel: String + // Label of fabricated root item + rootLabel: "ROOT", + + // query: String + // Specifies the set of children of the root item. + // example: + // | {type:'continent'} + query: null, + + // End of parameters to constructor + + constructor: function(params){ + // summary: + // Sets up variables, etc. + // tags: + // private + + // Make dummy root item + this.root = { + store: this, + root: true, + id: params.rootId, + label: params.rootLabel, + children: params.rootChildren // optional param + }; + }, + + // ======================================================================= + // Methods for traversing hierarchy + + mayHaveChildren: function(/*dojo.data.Item*/ item){ + // summary: + // Tells if an item has or may have children. Implementing logic here + // avoids showing +/- expando icon for nodes that we know don't have children. + // (For efficiency reasons we may not want to check if an element actually + // has children until user clicks the expando node) + // tags: + // extension + return item === this.root || this.inherited(arguments); + }, + + getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){ + // summary: + // Calls onComplete() with array of child items of given parent item, all loaded. + if(parentItem === this.root){ + if(this.root.children){ + // already loaded, just return + callback(this.root.children); + }else{ + this.store.fetch({ + query: this.query, + onComplete: dojo.hitch(this, function(items){ + this.root.children = items; + callback(items); + }), + onError: onError + }); + } + }else{ + this.inherited(arguments); + } + }, + + // ======================================================================= + // Inspecting items + + isItem: function(/* anything */ something){ + return (something === this.root) ? true : this.inherited(arguments); + }, + + fetchItemByIdentity: function(/* object */ keywordArgs){ + if(keywordArgs.identity == this.root.id){ + var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; + if(keywordArgs.onItem){ + keywordArgs.onItem.call(scope, this.root); + } + }else{ + this.inherited(arguments); + } + }, + + getIdentity: function(/* item */ item){ + return (item === this.root) ? this.root.id : this.inherited(arguments); + }, + + getLabel: function(/* item */ item){ + return (item === this.root) ? this.root.label : this.inherited(arguments); + }, + + // ======================================================================= + // Write interface + + newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ + // summary: + // Creates a new item. See dojo.data.api.Write for details on args. + // Used in drag & drop when item from external source dropped onto tree. + if(parent === this.root){ + this.onNewRootItem(args); + return this.store.newItem(args); + }else{ + return this.inherited(arguments); + } + }, + + onNewRootItem: function(args){ + // summary: + // User can override this method to modify a new element that's being + // added to the root of the tree, for example to add a flag like root=true + }, + + pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ + // summary: + // Move or copy an item from one parent item to another. + // Used in drag & drop + if(oldParentItem === this.root){ + if(!bCopy){ + // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches + // this.query... thus triggering an onChildrenChange() event to notify the Tree + // that this element is no longer a child of the root node + this.onLeaveRoot(childItem); + } + } + dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, childItem, + oldParentItem === this.root ? null : oldParentItem, + newParentItem === this.root ? null : newParentItem, + bCopy, + insertIndex + ); + if(newParentItem === this.root){ + // It's onAddToRoot()'s responsibility to modify the item so it matches + // this.query... thus triggering an onChildrenChange() event to notify the Tree + // that this element is now a child of the root node + this.onAddToRoot(childItem); + } + }, + + // ======================================================================= + // Handling for top level children + + onAddToRoot: function(/* item */ item){ + // summary: + // Called when item added to root of tree; user must override this method + // to modify the item so that it matches the query for top level items + // example: + // | store.setValue(item, "root", true); + // tags: + // extension + console.log(this, ": item ", item, " added to root"); + }, + + onLeaveRoot: function(/* item */ item){ + // summary: + // Called when item removed from root of tree; user must override this method + // to modify the item so it doesn't match the query for top level items + // example: + // | store.unsetAttribute(item, "root"); + // tags: + // extension + console.log(this, ": item ", item, " removed from root"); + }, + + // ======================================================================= + // Events from data store + + _requeryTop: function(){ + // reruns the query for the children of the root node, + // sending out an onSet notification if those children have changed + var oldChildren = this.root.children || []; + this.store.fetch({ + query: this.query, + onComplete: dojo.hitch(this, function(newChildren){ + this.root.children = newChildren; + + // If the list of children or the order of children has changed... + if(oldChildren.length != newChildren.length || + dojo.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){ + this.onChildrenChange(this.root, newChildren); + } + }) + }); + }, + + onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ + // summary: + // Handler for when new items appear in the store. Developers should override this + // method to be more efficient based on their app/data. + // description: + // Note that the default implementation requeries the top level items every time + // a new item is created, since any new item could be a top level item (even in + // addition to being a child of another item, since items can have multiple parents). + // + // Developers can override this function to do something more efficient if they can + // detect which items are possible top level items (based on the item and the + // parentInfo parameters). Often all top level items have parentInfo==null, but + // that will depend on which store you use and what your data is like. + // tags: + // extension + this._requeryTop(); + + this.inherited(arguments); + }, + + onDeleteItem: function(/*Object*/ item){ + // summary: + // Handler for delete notifications from underlying store + + // check if this was a child of root, and if so send notification that root's children + // have changed + if(dojo.indexOf(this.root.children, item) != -1){ + this._requeryTop(); + } + + this.inherited(arguments); + } +}); + + + +} + +if(!dojo._hasResource["dijit.Tree"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.Tree"] = true; +dojo.provide("dijit.Tree"); + + + + + + + + + + + +dojo.declare( + "dijit._TreeNode", + [dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained, dijit._CssStateMixin], +{ + // summary: + // Single node within a tree. This class is used internally + // by Tree and should not be accessed directly. + // tags: + // private + + // item: dojo.data.Item + // the dojo.data entry this tree represents + item: null, + + // isTreeNode: [protected] Boolean + // Indicates that this is a TreeNode. Used by `dijit.Tree` only, + // should not be accessed directly. + isTreeNode: true, + + // label: String + // Text of this tree node + label: "", + + // isExpandable: [private] Boolean + // This node has children, so show the expando node (+ sign) + isExpandable: null, + + // isExpanded: [readonly] Boolean + // This node is currently expanded (ie, opened) + isExpanded: false, + + // state: [private] String + // Dynamic loading-related stuff. + // When an empty folder node appears, it is "UNCHECKED" first, + // then after dojo.data query it becomes "LOADING" and, finally "LOADED" + state: "UNCHECKED", + + templateString: dojo.cache("dijit", "templates/TreeNode.html", "<div class=\"dijitTreeNode\" waiRole=\"presentation\"\n\t><div dojoAttachPoint=\"rowNode\" class=\"dijitTreeRow\" waiRole=\"presentation\" dojoAttachEvent=\"onmouseenter:_onMouseEnter, onmouseleave:_onMouseLeave, onclick:_onClick, ondblclick:_onDblClick\"\n\t\t><img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t\t/><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"contentNode\"\n\t\t\tclass=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"iconNode\" class=\"dijitIcon dijitTreeIcon\" waiRole=\"presentation\"\n\t\t\t/><span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"-1\" waiState=\"selected-false\" dojoAttachEvent=\"onfocus:_onLabelFocus\"></span>\n\t\t</span\n\t></div>\n\t<div dojoAttachPoint=\"containerNode\" class=\"dijitTreeContainer\" waiRole=\"presentation\" style=\"display: none;\"></div>\n</div>\n"), + + baseClass: "dijitTreeNode", + + // For hover effect for tree node, and focus effect for label + cssStateNodes: { + rowNode: "dijitTreeRow", + labelNode: "dijitTreeLabel" + }, + + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + label: {node: "labelNode", type: "innerText"}, + tooltip: {node: "rowNode", type: "attribute", attribute: "title"} + }), + + postCreate: function(){ + this.inherited(arguments); + + // set expand icon for leaf + this._setExpando(); + + // set icon and label class based on item + this._updateItemClasses(this.item); + + if(this.isExpandable){ + dijit.setWaiState(this.labelNode, "expanded", this.isExpanded); + } + }, + + _setIndentAttr: function(indent){ + // summary: + // Tell this node how many levels it should be indented + // description: + // 0 for top level nodes, 1 for their children, 2 for their + // grandchildren, etc. + this.indent = indent; + + // Math.max() is to prevent negative padding on hidden root node (when indent == -1) + var pixels = (Math.max(indent, 0) * this.tree._nodePixelIndent) + "px"; + + dojo.style(this.domNode, "backgroundPosition", pixels + " 0px"); + dojo.style(this.rowNode, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels); + + dojo.forEach(this.getChildren(), function(child){ + child.set("indent", indent+1); + }); + }, + + markProcessing: function(){ + // summary: + // Visually denote that tree is loading data, etc. + // tags: + // private + this.state = "LOADING"; + this._setExpando(true); + }, + + unmarkProcessing: function(){ + // summary: + // Clear markup from markProcessing() call + // tags: + // private + this._setExpando(false); + }, + + _updateItemClasses: function(item){ + // summary: + // Set appropriate CSS classes for icon and label dom node + // (used to allow for item updates to change respective CSS) + // tags: + // private + var tree = this.tree, model = tree.model; + if(tree._v10Compat && item === model.root){ + // For back-compat with 1.0, need to use null to specify root item (TODO: remove in 2.0) + item = null; + } + this._applyClassAndStyle(item, "icon", "Icon"); + this._applyClassAndStyle(item, "label", "Label"); + this._applyClassAndStyle(item, "row", "Row"); + }, + + _applyClassAndStyle: function(item, lower, upper){ + // summary: + // Set the appropriate CSS classes and styles for labels, icons and rows. + // + // item: + // The data item. + // + // lower: + // The lower case attribute to use, e.g. 'icon', 'label' or 'row'. + // + // upper: + // The upper case attribute to use, e.g. 'Icon', 'Label' or 'Row'. + // + // tags: + // private + + var clsName = "_" + lower + "Class"; + var nodeName = lower + "Node"; + + if(this[clsName]){ + dojo.removeClass(this[nodeName], this[clsName]); + } + this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded); + if(this[clsName]){ + dojo.addClass(this[nodeName], this[clsName]); + } + dojo.style(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {}); + }, + + _updateLayout: function(){ + // summary: + // Set appropriate CSS classes for this.domNode + // tags: + // private + var parent = this.getParent(); + if(!parent || parent.rowNode.style.display == "none"){ + /* if we are hiding the root node then make every first level child look like a root node */ + dojo.addClass(this.domNode, "dijitTreeIsRoot"); + }else{ + dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling()); + } + }, + + _setExpando: function(/*Boolean*/ processing){ + // summary: + // Set the right image for the expando node + // tags: + // private + + var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened", + "dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"], + _a11yStates = ["*","-","+","*"], + idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3); + + // apply the appropriate class to the expando node + dojo.removeClass(this.expandoNode, styles); + dojo.addClass(this.expandoNode, styles[idx]); + + // provide a non-image based indicator for images-off mode + this.expandoNodeText.innerHTML = _a11yStates[idx]; + + }, + + expand: function(){ + // summary: + // Show my children + // returns: + // Deferred that fires when expansion is complete + + // If there's already an expand in progress or we are already expanded, just return + if(this._expandDeferred){ + return this._expandDeferred; // dojo.Deferred + } + + // cancel in progress collapse operation + this._wipeOut && this._wipeOut.stop(); + + // All the state information for when a node is expanded, maybe this should be + // set when the animation completes instead + this.isExpanded = true; + dijit.setWaiState(this.labelNode, "expanded", "true"); + dijit.setWaiRole(this.containerNode, "group"); + dojo.addClass(this.contentNode,'dijitTreeContentExpanded'); + this._setExpando(); + this._updateItemClasses(this.item); + if(this == this.tree.rootNode){ + dijit.setWaiState(this.tree.domNode, "expanded", "true"); + } + + var def, + wipeIn = dojo.fx.wipeIn({ + node: this.containerNode, duration: dijit.defaultDuration, + onEnd: function(){ + def.callback(true); + } + }); + + // Deferred that fires when expand is complete + def = (this._expandDeferred = new dojo.Deferred(function(){ + // Canceller + wipeIn.stop(); + })); + + wipeIn.play(); + + return def; // dojo.Deferred + }, + + collapse: function(){ + // summary: + // Collapse this node (if it's expanded) + + if(!this.isExpanded){ return; } + + // cancel in progress expand operation + if(this._expandDeferred){ + this._expandDeferred.cancel(); + delete this._expandDeferred; + } + + this.isExpanded = false; + dijit.setWaiState(this.labelNode, "expanded", "false"); + if(this == this.tree.rootNode){ + dijit.setWaiState(this.tree.domNode, "expanded", "false"); + } + dojo.removeClass(this.contentNode,'dijitTreeContentExpanded'); + this._setExpando(); + this._updateItemClasses(this.item); + + if(!this._wipeOut){ + this._wipeOut = dojo.fx.wipeOut({ + node: this.containerNode, duration: dijit.defaultDuration + }); + } + this._wipeOut.play(); + }, + + // indent: Integer + // Levels from this node to the root node + indent: 0, + + setChildItems: function(/* Object[] */ items){ + // summary: + // Sets the child items of this node, removing/adding nodes + // from current children to match specified items[] array. + // Also, if this.persist == true, expands any children that were previously + // opened. + // returns: + // Deferred object that fires after all previously opened children + // have been expanded again (or fires instantly if there are no such children). + + var tree = this.tree, + model = tree.model, + defs = []; // list of deferreds that need to fire before I am complete + + + // Orphan all my existing children. + // If items contains some of the same items as before then we will reattach them. + // Don't call this.removeChild() because that will collapse the tree etc. + dojo.forEach(this.getChildren(), function(child){ + dijit._Container.prototype.removeChild.call(this, child); + }, this); + + this.state = "LOADED"; + + if(items && items.length > 0){ + this.isExpandable = true; + + // Create _TreeNode widget for each specified tree node, unless one already + // exists and isn't being used (presumably it's from a DnD move and was recently + // released + dojo.forEach(items, function(item){ + var id = model.getIdentity(item), + existingNodes = tree._itemNodesMap[id], + node; + if(existingNodes){ + for(var i=0;i<existingNodes.length;i++){ + if(existingNodes[i] && !existingNodes[i].getParent()){ + node = existingNodes[i]; + node.set('indent', this.indent+1); + break; + } + } + } + if(!node){ + node = this.tree._createTreeNode({ + item: item, + tree: tree, + isExpandable: model.mayHaveChildren(item), + label: tree.getLabel(item), + tooltip: tree.getTooltip(item), + dir: tree.dir, + lang: tree.lang, + indent: this.indent + 1 + }); + if(existingNodes){ + existingNodes.push(node); + }else{ + tree._itemNodesMap[id] = [node]; + } + } + this.addChild(node); + + // If node was previously opened then open it again now (this may trigger + // more data store accesses, recursively) + if(this.tree.autoExpand || this.tree._state(item)){ + defs.push(tree._expandNode(node)); + } + }, this); + + // note that updateLayout() needs to be called on each child after + // _all_ the children exist + dojo.forEach(this.getChildren(), function(child, idx){ + child._updateLayout(); + }); + }else{ + this.isExpandable=false; + } + + if(this._setExpando){ + // change expando to/from dot or + icon, as appropriate + this._setExpando(false); + } + + // Set leaf icon or folder icon, as appropriate + this._updateItemClasses(this.item); + + // On initial tree show, make the selected TreeNode as either the root node of the tree, + // or the first child, if the root node is hidden + if(this == tree.rootNode){ + var fc = this.tree.showRoot ? this : this.getChildren()[0]; + if(fc){ + fc.setFocusable(true); + tree.lastFocused = fc; + }else{ + // fallback: no nodes in tree so focus on Tree <div> itself + tree.domNode.setAttribute("tabIndex", "0"); + } + } + + return new dojo.DeferredList(defs); // dojo.Deferred + }, + + removeChild: function(/* treeNode */ node){ + this.inherited(arguments); + + var children = this.getChildren(); + if(children.length == 0){ + this.isExpandable = false; + this.collapse(); + } + + dojo.forEach(children, function(child){ + child._updateLayout(); + }); + }, + + makeExpandable: function(){ + // summary: + // if this node wasn't already showing the expando node, + // turn it into one and call _setExpando() + + // TODO: hmm this isn't called from anywhere, maybe should remove it for 2.0 + + this.isExpandable = true; + this._setExpando(false); + }, + + _onLabelFocus: function(evt){ + // summary: + // Called when this row is focused (possibly programatically) + // Note that we aren't using _onFocus() builtin to dijit + // because it's called when focus is moved to a descendant TreeNode. + // tags: + // private + this.tree._onNodeFocus(this); + }, + + setSelected: function(/*Boolean*/ selected){ + // summary: + // A Tree has a (single) currently selected node. + // Mark that this node is/isn't that currently selected node. + // description: + // In particular, setting a node as selected involves setting tabIndex + // so that when user tabs to the tree, focus will go to that node (only). + dijit.setWaiState(this.labelNode, "selected", selected); + dojo.toggleClass(this.rowNode, "dijitTreeRowSelected", selected); + }, + + setFocusable: function(/*Boolean*/ selected){ + // summary: + // A Tree has a (single) node that's focusable. + // Mark that this node is/isn't that currently focsuable node. + // description: + // In particular, setting a node as selected involves setting tabIndex + // so that when user tabs to the tree, focus will go to that node (only). + + this.labelNode.setAttribute("tabIndex", selected ? "0" : "-1"); + }, + + _onClick: function(evt){ + // summary: + // Handler for onclick event on a node + // tags: + // private + this.tree._onClick(this, evt); + }, + _onDblClick: function(evt){ + // summary: + // Handler for ondblclick event on a node + // tags: + // private + this.tree._onDblClick(this, evt); + }, + + _onMouseEnter: function(evt){ + // summary: + // Handler for onmouseenter event on a node + // tags: + // private + this.tree._onNodeMouseEnter(this, evt); + }, + + _onMouseLeave: function(evt){ + // summary: + // Handler for onmouseenter event on a node + // tags: + // private + this.tree._onNodeMouseLeave(this, evt); + } +}); + +dojo.declare( + "dijit.Tree", + [dijit._Widget, dijit._Templated], +{ + // summary: + // This widget displays hierarchical data from a store. + + // store: [deprecated] String||dojo.data.Store + // Deprecated. Use "model" parameter instead. + // The store to get data to display in the tree. + store: null, + + // model: dijit.Tree.model + // Interface to read tree data, get notifications of changes to tree data, + // and for handling drop operations (i.e drag and drop onto the tree) + model: null, + + // query: [deprecated] anything + // Deprecated. User should specify query to the model directly instead. + // Specifies datastore query to return the root item or top items for the tree. + query: null, + + // label: [deprecated] String + // Deprecated. Use dijit.tree.ForestStoreModel directly instead. + // Used in conjunction with query parameter. + // If a query is specified (rather than a root node id), and a label is also specified, + // then a fake root node is created and displayed, with this label. + label: "", + + // showRoot: [const] Boolean + // Should the root node be displayed, or hidden? + showRoot: true, + + // childrenAttr: [deprecated] String[] + // Deprecated. This information should be specified in the model. + // One ore more attributes that holds children of a tree node + childrenAttr: ["children"], + + // path: String[] or Item[] + // Full path from rootNode to selected node expressed as array of items or array of ids. + // Since setting the path may be asynchronous (because ofwaiting on dojo.data), set("path", ...) + // returns a Deferred to indicate when the set is complete. + path: [], + + // selectedItem: [readonly] Item + // The currently selected item in this tree. + // This property can only be set (via set('selectedItem', ...)) when that item is already + // visible in the tree. (I.e. the tree has already been expanded to show that node.) + // Should generally use `path` attribute to set the selected item instead. + selectedItem: null, + + // openOnClick: Boolean + // If true, clicking a folder node's label will open it, rather than calling onClick() + openOnClick: false, + + // openOnDblClick: Boolean + // If true, double-clicking a folder node's label will open it, rather than calling onDblClick() + openOnDblClick: false, + + templateString: dojo.cache("dijit", "templates/Tree.html", "<div class=\"dijitTree dijitTreeContainer\" waiRole=\"tree\"\n\tdojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" dojoAttachPoint=\"indentDetector\"></div>\n</div>\n"), + + // persist: Boolean + // Enables/disables use of cookies for state saving. + persist: true, + + // autoExpand: Boolean + // Fully expand the tree on load. Overrides `persist` + autoExpand: false, + + // dndController: [protected] String + // Class name to use as as the dnd controller. Specifying this class enables DnD. + // Generally you should specify this as "dijit.tree.dndSource". + dndController: null, + + // parameters to pull off of the tree and pass on to the dndController as its params + dndParams: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance", "dragThreshold", "betweenThreshold"], + + //declare the above items so they can be pulled from the tree's markup + + // onDndDrop: [protected] Function + // Parameter to dndController, see `dijit.tree.dndSource.onDndDrop`. + // Generally this doesn't need to be set. + onDndDrop: null, + + /*===== + itemCreator: function(nodes, target, source){ + // summary: + // Returns objects passed to `Tree.model.newItem()` based on DnD nodes + // dropped onto the tree. Developer must override this method to enable + // dropping from external sources onto this Tree, unless the Tree.model's items + // happen to look like {id: 123, name: "Apple" } with no other attributes. + // description: + // For each node in nodes[], which came from source, create a hash of name/value + // pairs to be passed to Tree.model.newItem(). Returns array of those hashes. + // nodes: DomNode[] + // The DOMNodes dragged from the source container + // target: DomNode + // The target TreeNode.rowNode + // source: dojo.dnd.Source + // The source container the nodes were dragged from, perhaps another Tree or a plain dojo.dnd.Source + // returns: Object[] + // Array of name/value hashes for each new item to be added to the Tree, like: + // | [ + // | { id: 123, label: "apple", foo: "bar" }, + // | { id: 456, label: "pear", zaz: "bam" } + // | ] + // tags: + // extension + return [{}]; + }, + =====*/ + itemCreator: null, + + // onDndCancel: [protected] Function + // Parameter to dndController, see `dijit.tree.dndSource.onDndCancel`. + // Generally this doesn't need to be set. + onDndCancel: null, + +/*===== + checkAcceptance: function(source, nodes){ + // summary: + // Checks if the Tree itself can accept nodes from this source + // source: dijit.tree._dndSource + // The source which provides items + // nodes: DOMNode[] + // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if + // source is a dijit.Tree. + // tags: + // extension + return true; // Boolean + }, +=====*/ + checkAcceptance: null, + +/*===== + checkItemAcceptance: function(target, source, position){ + // summary: + // Stub function to be overridden if one wants to check for the ability to drop at the node/item level + // description: + // In the base case, this is called to check if target can become a child of source. + // When betweenThreshold is set, position="before" or "after" means that we + // are asking if the source node can be dropped before/after the target node. + // target: DOMNode + // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to + // Use dijit.getEnclosingWidget(target) to get the TreeNode. + // source: dijit.tree.dndSource + // The (set of) nodes we are dropping + // position: String + // "over", "before", or "after" + // tags: + // extension + return true; // Boolean + }, +=====*/ + checkItemAcceptance: null, + + // dragThreshold: Integer + // Number of pixels mouse moves before it's considered the start of a drag operation + dragThreshold: 5, + + // betweenThreshold: Integer + // Set to a positive value to allow drag and drop "between" nodes. + // + // If during DnD mouse is over a (target) node but less than betweenThreshold + // pixels from the bottom edge, dropping the the dragged node will make it + // the next sibling of the target node, rather than the child. + // + // Similarly, if mouse is over a target node but less that betweenThreshold + // pixels from the top edge, dropping the dragged node will make it + // the target node's previous sibling rather than the target node's child. + betweenThreshold: 0, + + // _nodePixelIndent: Integer + // Number of pixels to indent tree nodes (relative to parent node). + // Default is 19 but can be overridden by setting CSS class dijitTreeIndent + // and calling resize() or startup() on tree after it's in the DOM. + _nodePixelIndent: 19, + + _publish: function(/*String*/ topicName, /*Object*/ message){ + // summary: + // Publish a message for this widget/topic + dojo.publish(this.id, [dojo.mixin({tree: this, event: topicName}, message || {})]); + }, + + postMixInProperties: function(){ + this.tree = this; + + if(this.autoExpand){ + // There's little point in saving opened/closed state of nodes for a Tree + // that initially opens all it's nodes. + this.persist = false; + } + + this._itemNodesMap={}; + + if(!this.cookieName){ + this.cookieName = this.id + "SaveStateCookie"; + } + + this._loadDeferred = new dojo.Deferred(); + + this.inherited(arguments); + }, + + postCreate: function(){ + this._initState(); + + // Create glue between store and Tree, if not specified directly by user + if(!this.model){ + this._store2model(); + } + + // monitor changes to items + this.connect(this.model, "onChange", "_onItemChange"); + this.connect(this.model, "onChildrenChange", "_onItemChildrenChange"); + this.connect(this.model, "onDelete", "_onItemDelete"); + + this._load(); + + this.inherited(arguments); + + if(this.dndController){ + if(dojo.isString(this.dndController)){ + this.dndController = dojo.getObject(this.dndController); + } + var params={}; + for(var i=0; i<this.dndParams.length;i++){ + if(this[this.dndParams[i]]){ + params[this.dndParams[i]] = this[this.dndParams[i]]; + } + } + this.dndController = new this.dndController(this, params); + } + }, + + _store2model: function(){ + // summary: + // User specified a store&query rather than model, so create model from store/query + this._v10Compat = true; + dojo.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query"); + + var modelParams = { + id: this.id + "_ForestStoreModel", + store: this.store, + query: this.query, + childrenAttrs: this.childrenAttr + }; + + // Only override the model's mayHaveChildren() method if the user has specified an override + if(this.params.mayHaveChildren){ + modelParams.mayHaveChildren = dojo.hitch(this, "mayHaveChildren"); + } + + if(this.params.getItemChildren){ + modelParams.getChildren = dojo.hitch(this, function(item, onComplete, onError){ + this.getItemChildren((this._v10Compat && item === this.model.root) ? null : item, onComplete, onError); + }); + } + this.model = new dijit.tree.ForestStoreModel(modelParams); + + // For backwards compatibility, the visibility of the root node is controlled by + // whether or not the user has specified a label + this.showRoot = Boolean(this.label); + }, + + onLoad: function(){ + // summary: + // Called when tree finishes loading and expanding. + // description: + // If persist == true the loading may encompass many levels of fetches + // from the data store, each asynchronous. Waits for all to finish. + // tags: + // callback + }, + + _load: function(){ + // summary: + // Initial load of the tree. + // Load root node (possibly hidden) and it's children. + this.model.getRoot( + dojo.hitch(this, function(item){ + var rn = (this.rootNode = this.tree._createTreeNode({ + item: item, + tree: this, + isExpandable: true, + label: this.label || this.getLabel(item), + indent: this.showRoot ? 0 : -1 + })); + if(!this.showRoot){ + rn.rowNode.style.display="none"; + } + this.domNode.appendChild(rn.domNode); + var identity = this.model.getIdentity(item); + if(this._itemNodesMap[identity]){ + this._itemNodesMap[identity].push(rn); + }else{ + this._itemNodesMap[identity] = [rn]; + } + + rn._updateLayout(); // sets "dijitTreeIsRoot" CSS classname + + // load top level children and then fire onLoad() event + this._expandNode(rn).addCallback(dojo.hitch(this, function(){ + this._loadDeferred.callback(true); + this.onLoad(); + })); + }), + function(err){ + console.error(this, ": error loading root: ", err); + } + ); + }, + + getNodesByItem: function(/*dojo.data.Item or id*/ item){ + // summary: + // Returns all tree nodes that refer to an item + // returns: + // Array of tree nodes that refer to passed item + + if(!item){ return []; } + var identity = dojo.isString(item) ? item : this.model.getIdentity(item); + // return a copy so widget don't get messed up by changes to returned array + return [].concat(this._itemNodesMap[identity]); + }, + + _setSelectedItemAttr: function(/*dojo.data.Item or id*/ item){ + // summary: + // Select a tree node related to passed item. + // WARNING: if model use multi-parented items or desired tree node isn't already loaded + // behavior is undefined. Use set('path', ...) instead. + + var oldValue = this.get("selectedItem"); + var identity = (!item || dojo.isString(item)) ? item : this.model.getIdentity(item); + if(identity == oldValue ? this.model.getIdentity(oldValue) : null){ return; } + var nodes = this._itemNodesMap[identity]; + this._selectNode((nodes && nodes[0]) || null); //select the first item + }, + + _getSelectedItemAttr: function(){ + // summary: + // Return item related to selected tree node. + return this.selectedNode && this.selectedNode.item; + }, + + _setPathAttr: function(/*Item[] || String[]*/ path){ + // summary: + // Select the tree node identified by passed path. + // path: + // Array of items or item id's + // returns: + // Deferred to indicate when the set is complete + + var d = new dojo.Deferred(); + + this._selectNode(null); + if(!path || !path.length){ + d.resolve(true); + return d; + } + + // If this is called during initialization, defer running until Tree has finished loading + this._loadDeferred.addCallback(dojo.hitch(this, function(){ + if(!this.rootNode){ + d.reject(new Error("!this.rootNode")); + return; + } + if(path[0] !== this.rootNode.item && (dojo.isString(path[0]) && path[0] != this.model.getIdentity(this.rootNode.item))){ + d.reject(new Error(this.id + ":path[0] doesn't match this.rootNode.item. Maybe you are using the wrong tree.")); + return; + } + path.shift(); + + var node = this.rootNode; + + function advance(){ + // summary: + // Called when "node" has completed loading and expanding. Pop the next item from the path + // (which must be a child of "node") and advance to it, and then recurse. + + // Set item and identity to next item in path (node is pointing to the item that was popped + // from the path _last_ time. + var item = path.shift(), + identity = dojo.isString(item) ? item : this.model.getIdentity(item); + + // Change "node" from previous item in path to the item we just popped from path + dojo.some(this._itemNodesMap[identity], function(n){ + if(n.getParent() == node){ + node = n; + return true; + } + return false; + }); + + if(path.length){ + // Need to do more expanding + this._expandNode(node).addCallback(dojo.hitch(this, advance)); + }else{ + // Final destination node, select it + this._selectNode(node); + + // signal that path setting is finished + d.resolve(true); + } + } + + this._expandNode(node).addCallback(dojo.hitch(this, advance)); + })); + + return d; + }, + + _getPathAttr: function(){ + // summary: + // Return an array of items that is the path to selected tree node. + if(!this.selectedNode){ return; } + var res = []; + var treeNode = this.selectedNode; + while(treeNode && treeNode !== this.rootNode){ + res.unshift(treeNode.item); + treeNode = treeNode.getParent(); + } + res.unshift(this.rootNode.item); + return res; + }, + + ////////////// Data store related functions ////////////////////// + // These just get passed to the model; they are here for back-compat + + mayHaveChildren: function(/*dojo.data.Item*/ item){ + // summary: + // Deprecated. This should be specified on the model itself. + // + // Overridable function to tell if an item has or may have children. + // Controls whether or not +/- expando icon is shown. + // (For efficiency reasons we may not want to check if an element actually + // has children until user clicks the expando node) + // tags: + // deprecated + }, + + getItemChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){ + // summary: + // Deprecated. This should be specified on the model itself. + // + // Overridable function that return array of child items of given parent item, + // or if parentItem==null then return top items in tree + // tags: + // deprecated + }, + + /////////////////////////////////////////////////////// + // Functions for converting an item to a TreeNode + getLabel: function(/*dojo.data.Item*/ item){ + // summary: + // Overridable function to get the label for a tree node (given the item) + // tags: + // extension + return this.model.getLabel(item); // String + }, + + getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + // summary: + // Overridable function to return CSS class name to display icon + // tags: + // extension + return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf" + }, + + getLabelClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + // summary: + // Overridable function to return CSS class name to display label + // tags: + // extension + }, + + getRowClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + // summary: + // Overridable function to return CSS class name to display row + // tags: + // extension + }, + + getIconStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + // summary: + // Overridable function to return CSS styles to display icon + // returns: + // Object suitable for input to dojo.style() like {backgroundImage: "url(...)"} + // tags: + // extension + }, + + getLabelStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + // summary: + // Overridable function to return CSS styles to display label + // returns: + // Object suitable for input to dojo.style() like {color: "red", background: "green"} + // tags: + // extension + }, + + getRowStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + // summary: + // Overridable function to return CSS styles to display row + // returns: + // Object suitable for input to dojo.style() like {background-color: "#bbb"} + // tags: + // extension + }, + + getTooltip: function(/*dojo.data.Item*/ item){ + // summary: + // Overridable function to get the tooltip for a tree node (given the item) + // tags: + // extension + return ""; // String + }, + + /////////// Keyboard and Mouse handlers //////////////////// + + _onKeyPress: function(/*Event*/ e){ + // summary: + // Translates keypress events into commands for the controller + if(e.altKey){ return; } + var dk = dojo.keys; + var treeNode = dijit.getEnclosingWidget(e.target); + if(!treeNode){ return; } + + var key = e.charOrCode; + if(typeof key == "string"){ // handle printables (letter navigation) + // Check for key navigation. + if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){ + this._onLetterKeyNav( { node: treeNode, key: key.toLowerCase() } ); + dojo.stopEvent(e); + } + }else{ // handle non-printables (arrow keys) + // clear record of recent printables (being saved for multi-char letter navigation), + // because "a", down-arrow, "b" shouldn't search for "ab" + if(this._curSearch){ + clearTimeout(this._curSearch.timer); + delete this._curSearch; + } + + var map = this._keyHandlerMap; + if(!map){ + // setup table mapping keys to events + map = {}; + map[dk.ENTER]="_onEnterKey"; + map[this.isLeftToRight() ? dk.LEFT_ARROW : dk.RIGHT_ARROW]="_onLeftArrow"; + map[this.isLeftToRight() ? dk.RIGHT_ARROW : dk.LEFT_ARROW]="_onRightArrow"; + map[dk.UP_ARROW]="_onUpArrow"; + map[dk.DOWN_ARROW]="_onDownArrow"; + map[dk.HOME]="_onHomeKey"; + map[dk.END]="_onEndKey"; + this._keyHandlerMap = map; + } + if(this._keyHandlerMap[key]){ + this[this._keyHandlerMap[key]]( { node: treeNode, item: treeNode.item, evt: e } ); + dojo.stopEvent(e); + } + } + }, + + _onEnterKey: function(/*Object*/ message, /*Event*/ evt){ + this._publish("execute", { item: message.item, node: message.node } ); + this._selectNode(message.node); + this.onClick(message.item, message.node, evt); + }, + + _onDownArrow: function(/*Object*/ message){ + // summary: + // down arrow pressed; get next visible node, set focus there + var node = this._getNextNode(message.node); + if(node && node.isTreeNode){ + this.focusNode(node); + } + }, + + _onUpArrow: function(/*Object*/ message){ + // summary: + // Up arrow pressed; move to previous visible node + + var node = message.node; + + // if younger siblings + var previousSibling = node.getPreviousSibling(); + if(previousSibling){ + node = previousSibling; + // if the previous node is expanded, dive in deep + while(node.isExpandable && node.isExpanded && node.hasChildren()){ + // move to the last child + var children = node.getChildren(); + node = children[children.length-1]; + } + }else{ + // if this is the first child, return the parent + // unless the parent is the root of a tree with a hidden root + var parent = node.getParent(); + if(!(!this.showRoot && parent === this.rootNode)){ + node = parent; + } + } + + if(node && node.isTreeNode){ + this.focusNode(node); + } + }, + + _onRightArrow: function(/*Object*/ message){ + // summary: + // Right arrow pressed; go to child node + var node = message.node; + + // if not expanded, expand, else move to 1st child + if(node.isExpandable && !node.isExpanded){ + this._expandNode(node); + }else if(node.hasChildren()){ + node = node.getChildren()[0]; + if(node && node.isTreeNode){ + this.focusNode(node); + } + } + }, + + _onLeftArrow: function(/*Object*/ message){ + // summary: + // Left arrow pressed. + // If not collapsed, collapse, else move to parent. + + var node = message.node; + + if(node.isExpandable && node.isExpanded){ + this._collapseNode(node); + }else{ + var parent = node.getParent(); + if(parent && parent.isTreeNode && !(!this.showRoot && parent === this.rootNode)){ + this.focusNode(parent); + } + } + }, + + _onHomeKey: function(){ + // summary: + // Home key pressed; get first visible node, and set focus there + var node = this._getRootOrFirstNode(); + if(node){ + this.focusNode(node); + } + }, + + _onEndKey: function(/*Object*/ message){ + // summary: + // End key pressed; go to last visible node. + + var node = this.rootNode; + while(node.isExpanded){ + var c = node.getChildren(); + node = c[c.length - 1]; + } + + if(node && node.isTreeNode){ + this.focusNode(node); + } + }, + + // multiCharSearchDuration: Number + // If multiple characters are typed where each keystroke happens within + // multiCharSearchDuration of the previous keystroke, + // search for nodes matching all the keystrokes. + // + // For example, typing "ab" will search for entries starting with + // "ab" unless the delay between "a" and "b" is greater than multiCharSearchDuration. + multiCharSearchDuration: 250, + + _onLetterKeyNav: function(message){ + // summary: + // Called when user presses a prinatable key; search for node starting with recently typed letters. + // message: Object + // Like { node: TreeNode, key: 'a' } where key is the key the user pressed. + + // Branch depending on whether this key starts a new search, or modifies an existing search + var cs = this._curSearch; + if(cs){ + // We are continuing a search. Ex: user has pressed 'a', and now has pressed + // 'b', so we want to search for nodes starting w/"ab". + cs.pattern = cs.pattern + message.key; + clearTimeout(cs.timer); + }else{ + // We are starting a new search + cs = this._curSearch = { + pattern: message.key, + startNode: message.node + }; + } + + // set/reset timer to forget recent keystrokes + var self = this; + cs.timer = setTimeout(function(){ + delete self._curSearch; + }, this.multiCharSearchDuration); + + // Navigate to TreeNode matching keystrokes [entered so far]. + var node = cs.startNode; + do{ + node = this._getNextNode(node); + //check for last node, jump to first node if necessary + if(!node){ + node = this._getRootOrFirstNode(); + } + }while(node !== cs.startNode && (node.label.toLowerCase().substr(0, cs.pattern.length) != cs.pattern)); + if(node && node.isTreeNode){ + // no need to set focus if back where we started + if(node !== cs.startNode){ + this.focusNode(node); + } + } + }, + + _onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){ + // summary: + // Translates click events into commands for the controller to process + + var domElement = e.target, + isExpandoClick = (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText); + + if( (this.openOnClick && nodeWidget.isExpandable) || isExpandoClick ){ + // expando node was clicked, or label of a folder node was clicked; open it + if(nodeWidget.isExpandable){ + this._onExpandoClick({node:nodeWidget}); + } + }else{ + this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } ); + this.onClick(nodeWidget.item, nodeWidget, e); + this.focusNode(nodeWidget); + } + if(!isExpandoClick){ + this._selectNode(nodeWidget); + } + dojo.stopEvent(e); + }, + _onDblClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){ + // summary: + // Translates double-click events into commands for the controller to process + + var domElement = e.target, + isExpandoClick = (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText); + + if( (this.openOnDblClick && nodeWidget.isExpandable) ||isExpandoClick ){ + // expando node was clicked, or label of a folder node was clicked; open it + if(nodeWidget.isExpandable){ + this._onExpandoClick({node:nodeWidget}); + } + }else{ + this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } ); + this.onDblClick(nodeWidget.item, nodeWidget, e); + this.focusNode(nodeWidget); + } + if(!isExpandoClick){ + this._selectNode(nodeWidget); + } + dojo.stopEvent(e); + }, + + _onExpandoClick: function(/*Object*/ message){ + // summary: + // User clicked the +/- icon; expand or collapse my children. + var node = message.node; + + // If we are collapsing, we might be hiding the currently focused node. + // Also, clicking the expando node might have erased focus from the current node. + // For simplicity's sake just focus on the node with the expando. + this.focusNode(node); + + if(node.isExpanded){ + this._collapseNode(node); + }else{ + this._expandNode(node); + } + }, + + onClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){ + // summary: + // Callback when a tree node is clicked + // tags: + // callback + }, + onDblClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){ + // summary: + // Callback when a tree node is double-clicked + // tags: + // callback + }, + onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){ + // summary: + // Callback when a node is opened + // tags: + // callback + }, + onClose: function(/* dojo.data */ item, /*TreeNode*/ node){ + // summary: + // Callback when a node is closed + // tags: + // callback + }, + + _getNextNode: function(node){ + // summary: + // Get next visible node + + if(node.isExpandable && node.isExpanded && node.hasChildren()){ + // if this is an expanded node, get the first child + return node.getChildren()[0]; // _TreeNode + }else{ + // find a parent node with a sibling + while(node && node.isTreeNode){ + var returnNode = node.getNextSibling(); + if(returnNode){ + return returnNode; // _TreeNode + } + node = node.getParent(); + } + return null; + } + }, + + _getRootOrFirstNode: function(){ + // summary: + // Get first visible node + return this.showRoot ? this.rootNode : this.rootNode.getChildren()[0]; + }, + + _collapseNode: function(/*_TreeNode*/ node){ + // summary: + // Called when the user has requested to collapse the node + + if(node._expandNodeDeferred){ + delete node._expandNodeDeferred; + } + + if(node.isExpandable){ + if(node.state == "LOADING"){ + // ignore clicks while we are in the process of loading data + return; + } + + node.collapse(); + this.onClose(node.item, node); + + if(node.item){ + this._state(node.item,false); + this._saveState(); + } + } + }, + + _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){ + // summary: + // Called when the user has requested to expand the node + // recursive: + // Internal flag used when _expandNode() calls itself, don't set. + // returns: + // Deferred that fires when the node is loaded and opened and (if persist=true) all it's descendants + // that were previously opened too + + if(node._expandNodeDeferred && !recursive){ + // there's already an expand in progress (or completed), so just return + return node._expandNodeDeferred; // dojo.Deferred + } + + var model = this.model, + item = node.item, + _this = this; + + switch(node.state){ + case "UNCHECKED": + // need to load all the children, and then expand + node.markProcessing(); + + // Setup deferred to signal when the load and expand are finished. + // Save that deferred in this._expandDeferred as a flag that operation is in progress. + var def = (node._expandNodeDeferred = new dojo.Deferred()); + + // Get the children + model.getChildren( + item, + function(items){ + node.unmarkProcessing(); + + // Display the children and also start expanding any children that were previously expanded + // (if this.persist == true). The returned Deferred will fire when those expansions finish. + var scid = node.setChildItems(items); + + // Call _expandNode() again but this time it will just to do the animation (default branch). + // The returned Deferred will fire when the animation completes. + // TODO: seems like I can avoid recursion and just use a deferred to sequence the events? + var ed = _this._expandNode(node, true); + + // After the above two tasks (setChildItems() and recursive _expandNode()) finish, + // signal that I am done. + scid.addCallback(function(){ + ed.addCallback(function(){ + def.callback(); + }) + }); + }, + function(err){ + console.error(_this, ": error loading root children: ", err); + } + ); + break; + + default: // "LOADED" + // data is already loaded; just expand node + def = (node._expandNodeDeferred = node.expand()); + + this.onOpen(node.item, node); + + if(item){ + this._state(item, true); + this._saveState(); + } + } + + return def; // dojo.Deferred + }, + + ////////////////// Miscellaneous functions //////////////// + + focusNode: function(/* _tree.Node */ node){ + // summary: + // Focus on the specified node (which must be visible) + // tags: + // protected + + // set focus so that the label will be voiced using screen readers + dijit.focus(node.labelNode); + }, + + _selectNode: function(/*_tree.Node*/ node){ + // summary: + // Mark specified node as select, and unmark currently selected node. + // tags: + // protected + + if(this.selectedNode && !this.selectedNode._destroyed){ + this.selectedNode.setSelected(false); + } + if(node){ + node.setSelected(true); + } + this.selectedNode = node; + }, + + _onNodeFocus: function(/*dijit._Widget*/ node){ + // summary: + // Called when a TreeNode gets focus, either by user clicking + // it, or programatically by arrow key handling code. + // description: + // It marks that the current node is the selected one, and the previously + // selected node no longer is. + + if(node && node != this.lastFocused){ + if(this.lastFocused && !this.lastFocused._destroyed){ + // mark that the previously focsable node is no longer focusable + this.lastFocused.setFocusable(false); + } + + // mark that the new node is the currently selected one + node.setFocusable(true); + this.lastFocused = node; + } + }, + + _onNodeMouseEnter: function(/*dijit._Widget*/ node){ + // summary: + // Called when mouse is over a node (onmouseenter event), + // this is monitored by the DND code + }, + + _onNodeMouseLeave: function(/*dijit._Widget*/ node){ + // summary: + // Called when mouse leaves a node (onmouseleave event), + // this is monitored by the DND code + }, + + //////////////// Events from the model ////////////////////////// + + _onItemChange: function(/*Item*/ item){ + // summary: + // Processes notification of a change to an item's scalar values like label + var model = this.model, + identity = model.getIdentity(item), + nodes = this._itemNodesMap[identity]; + + if(nodes){ + var label = this.getLabel(item), + tooltip = this.getTooltip(item); + dojo.forEach(nodes, function(node){ + node.set({ + item: item, // theoretically could be new JS Object representing same item + label: label, + tooltip: tooltip + }); + node._updateItemClasses(item); + }); + } + }, + + _onItemChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ + // summary: + // Processes notification of a change to an item's children + var model = this.model, + identity = model.getIdentity(parent), + parentNodes = this._itemNodesMap[identity]; + + if(parentNodes){ + dojo.forEach(parentNodes,function(parentNode){ + parentNode.setChildItems(newChildrenList); + }); + } + }, + + _onItemDelete: function(/*Item*/ item){ + // summary: + // Processes notification of a deletion of an item + var model = this.model, + identity = model.getIdentity(item), + nodes = this._itemNodesMap[identity]; + + if(nodes){ + dojo.forEach(nodes,function(node){ + var parent = node.getParent(); + if(parent){ + // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call... + parent.removeChild(node); + } + node.destroyRecursive(); + }); + delete this._itemNodesMap[identity]; + } + }, + + /////////////// Miscellaneous funcs + + _initState: function(){ + // summary: + // Load in which nodes should be opened automatically + if(this.persist){ + var cookie = dojo.cookie(this.cookieName); + this._openedItemIds = {}; + if(cookie){ + dojo.forEach(cookie.split(','), function(item){ + this._openedItemIds[item] = true; + }, this); + } + } + }, + _state: function(item,expanded){ + // summary: + // Query or set expanded state for an item, + if(!this.persist){ + return false; + } + var id=this.model.getIdentity(item); + if(arguments.length === 1){ + return this._openedItemIds[id]; + } + if(expanded){ + this._openedItemIds[id] = true; + }else{ + delete this._openedItemIds[id]; + } + }, + _saveState: function(){ + // summary: + // Create and save a cookie with the currently expanded nodes identifiers + if(!this.persist){ + return; + } + var ary = []; + for(var id in this._openedItemIds){ + ary.push(id); + } + dojo.cookie(this.cookieName, ary.join(","), {expires:365}); + }, + + destroy: function(){ + if(this._curSearch){ + clearTimeout(this._curSearch.timer); + delete this._curSearch; + } + if(this.rootNode){ + this.rootNode.destroyRecursive(); + } + if(this.dndController && !dojo.isString(this.dndController)){ + this.dndController.destroy(); + } + this.rootNode = null; + this.inherited(arguments); + }, + + destroyRecursive: function(){ + // A tree is treated as a leaf, not as a node with children (like a grid), + // but defining destroyRecursive for back-compat. + this.destroy(); + }, + + resize: function(changeSize){ + if(changeSize){ + dojo.marginBox(this.domNode, changeSize); + dojo.style(this.domNode, "overflow", "auto"); // for scrollbars + } + + // The only JS sizing involved w/tree is the indentation, which is specified + // in CSS and read in through this dummy indentDetector node (tree must be + // visible and attached to the DOM to read this) + this._nodePixelIndent = dojo.marginBox(this.tree.indentDetector).w; + + if(this.tree.rootNode){ + // If tree has already loaded, then reset indent for all the nodes + this.tree.rootNode.set('indent', this.showRoot ? 0 : -1); + } + }, + + _createTreeNode: function(/*Object*/ args){ + // summary: + // creates a TreeNode + // description: + // Developers can override this method to define their own TreeNode class; + // However it will probably be removed in a future release in favor of a way + // of just specifying a widget for the label, rather than one that contains + // the children too. + return new dijit._TreeNode(args); + } +}); + +// For back-compat. TODO: remove in 2.0 + + + +} + +if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.Container"] = true; +dojo.provide("dojo.dnd.Container"); + + + + +/* + Container states: + "" - normal state + "Over" - mouse over a container + Container item states: + "" - normal state + "Over" - mouse over a container item +*/ + +/*===== +dojo.declare("dojo.dnd.__ContainerArgs", [], { + creator: function(){ + // summary: + // a creator function, which takes a data item, and returns an object like that: + // {node: newNode, data: usedData, type: arrayOfStrings} + }, + + // skipForm: Boolean + // don't start the drag operation, if clicked on form elements + skipForm: false, + + // dropParent: Node||String + // node or node's id to use as the parent node for dropped items + // (must be underneath the 'node' parameter in the DOM) + dropParent: null, + + // _skipStartup: Boolean + // skip startup(), which collects children, for deferred initialization + // (this is used in the markup mode) + _skipStartup: false +}); + +dojo.dnd.Item = function(){ + // summary: + // Represents (one of) the source node(s) being dragged. + // Contains (at least) the "type" and "data" attributes. + // type: String[] + // Type(s) of this item, by default this is ["text"] + // data: Object + // Logical representation of the object being dragged. + // If the drag object's type is "text" then data is a String, + // if it's another type then data could be a different Object, + // perhaps a name/value hash. + + this.type = type; + this.data = data; +} +=====*/ + +dojo.declare("dojo.dnd.Container", null, { + // summary: + // a Container object, which knows when mouse hovers over it, + // and over which element it hovers + + // object attributes (for markup) + skipForm: false, + + /*===== + // current: DomNode + // The DOM node the mouse is currently hovered over + current: null, + + // map: Hash<String, dojo.dnd.Item> + // Map from an item's id (which is also the DOMNode's id) to + // the dojo.dnd.Item itself. + map: {}, + =====*/ + + constructor: function(node, params){ + // summary: + // a constructor of the Container + // node: Node + // node or node's id to build the container on + // params: dojo.dnd.__ContainerArgs + // a dictionary of parameters + this.node = dojo.byId(node); + if(!params){ params = {}; } + this.creator = params.creator || null; + this.skipForm = params.skipForm; + this.parent = params.dropParent && dojo.byId(params.dropParent); + + // class-specific variables + this.map = {}; + this.current = null; + + // states + this.containerState = ""; + dojo.addClass(this.node, "dojoDndContainer"); + + // mark up children + if(!(params && params._skipStartup)){ + this.startup(); + } + + // set up events + this.events = [ + dojo.connect(this.node, "onmouseover", this, "onMouseOver"), + dojo.connect(this.node, "onmouseout", this, "onMouseOut"), + // cancel text selection and text dragging + dojo.connect(this.node, "ondragstart", this, "onSelectStart"), + dojo.connect(this.node, "onselectstart", this, "onSelectStart") + ]; + }, + + // object attributes (for markup) + creator: function(){ + // summary: + // creator function, dummy at the moment + }, + + // abstract access to the map + getItem: function(/*String*/ key){ + // summary: + // returns a data item by its key (id) + return this.map[key]; // dojo.dnd.Item + }, + setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){ + // summary: + // associates a data item with its key (id) + this.map[key] = data; + }, + delItem: function(/*String*/ key){ + // summary: + // removes a data item from the map by its key (id) + delete this.map[key]; + }, + forInItems: function(/*Function*/ f, /*Object?*/ o){ + // summary: + // iterates over a data map skipping members that + // are present in the empty object (IE and/or 3rd-party libraries). + o = o || dojo.global; + var m = this.map, e = dojo.dnd._empty; + for(var i in m){ + if(i in e){ continue; } + f.call(o, m[i], i, this); + } + return o; // Object + }, + clearItems: function(){ + // summary: + // removes all data items from the map + this.map = {}; + }, + + // methods + getAllNodes: function(){ + // summary: + // returns a list (an array) of all valid child nodes + return dojo.query("> .dojoDndItem", this.parent); // NodeList + }, + sync: function(){ + // summary: + // sync up the node list with the data map + var map = {}; + this.getAllNodes().forEach(function(node){ + if(node.id){ + var item = this.getItem(node.id); + if(item){ + map[node.id] = item; + return; + } + }else{ + node.id = dojo.dnd.getUniqueId(); + } + var type = node.getAttribute("dndType"), + data = node.getAttribute("dndData"); + map[node.id] = { + data: data || node.innerHTML, + type: type ? type.split(/\s*,\s*/) : ["text"] + }; + }, this); + this.map = map; + return this; // self + }, + insertNodes: function(data, before, anchor){ + // summary: + // inserts an array of new nodes before/after an anchor node + // data: Array + // a list of data items, which should be processed by the creator function + // before: Boolean + // insert before the anchor, if true, and after the anchor otherwise + // anchor: Node + // the anchor node to be used as a point of insertion + if(!this.parent.firstChild){ + anchor = null; + }else if(before){ + if(!anchor){ + anchor = this.parent.firstChild; + } + }else{ + if(anchor){ + anchor = anchor.nextSibling; + } + } + if(anchor){ + for(var i = 0; i < data.length; ++i){ + var t = this._normalizedCreator(data[i]); + this.setItem(t.node.id, {data: t.data, type: t.type}); + this.parent.insertBefore(t.node, anchor); + } + }else{ + for(var i = 0; i < data.length; ++i){ + var t = this._normalizedCreator(data[i]); + this.setItem(t.node.id, {data: t.data, type: t.type}); + this.parent.appendChild(t.node); + } + } + return this; // self + }, + destroy: function(){ + // summary: + // prepares this object to be garbage-collected + dojo.forEach(this.events, dojo.disconnect); + this.clearItems(); + this.node = this.parent = this.current = null; + }, + + // markup methods + markupFactory: function(params, node){ + params._skipStartup = true; + return new dojo.dnd.Container(node, params); + }, + startup: function(){ + // summary: + // collects valid child items and populate the map + + // set up the real parent node + if(!this.parent){ + // use the standard algorithm, if not assigned + this.parent = this.node; + if(this.parent.tagName.toLowerCase() == "table"){ + var c = this.parent.getElementsByTagName("tbody"); + if(c && c.length){ this.parent = c[0]; } + } + } + this.defaultCreator = dojo.dnd._defaultCreator(this.parent); + + // process specially marked children + this.sync(); + }, + + // mouse events + onMouseOver: function(e){ + // summary: + // event processor for onmouseover + // e: Event + // mouse event + var n = e.relatedTarget; + while(n){ + if(n == this.node){ break; } + try{ + n = n.parentNode; + }catch(x){ + n = null; + } + } + if(!n){ + this._changeState("Container", "Over"); + this.onOverEvent(); + } + n = this._getChildByEvent(e); + if(this.current == n){ return; } + if(this.current){ this._removeItemClass(this.current, "Over"); } + if(n){ this._addItemClass(n, "Over"); } + this.current = n; + }, + onMouseOut: function(e){ + // summary: + // event processor for onmouseout + // e: Event + // mouse event + for(var n = e.relatedTarget; n;){ + if(n == this.node){ return; } + try{ + n = n.parentNode; + }catch(x){ + n = null; + } + } + if(this.current){ + this._removeItemClass(this.current, "Over"); + this.current = null; + } + this._changeState("Container", ""); + this.onOutEvent(); + }, + onSelectStart: function(e){ + // summary: + // event processor for onselectevent and ondragevent + // e: Event + // mouse event + if(!this.skipForm || !dojo.dnd.isFormElement(e)){ + dojo.stopEvent(e); + } + }, + + // utilities + onOverEvent: function(){ + // summary: + // this function is called once, when mouse is over our container + }, + onOutEvent: function(){ + // summary: + // this function is called once, when mouse is out of our container + }, + _changeState: function(type, newState){ + // summary: + // changes a named state to new state value + // type: String + // a name of the state to change + // newState: String + // new state + var prefix = "dojoDnd" + type; + var state = type.toLowerCase() + "State"; + //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]); + dojo.removeClass(this.node, prefix + this[state]); + dojo.addClass(this.node, prefix + newState); + this[state] = newState; + }, + _addItemClass: function(node, type){ + // summary: + // adds a class with prefix "dojoDndItem" + // node: Node + // a node + // type: String + // a variable suffix for a class name + dojo.addClass(node, "dojoDndItem" + type); + }, + _removeItemClass: function(node, type){ + // summary: + // removes a class with prefix "dojoDndItem" + // node: Node + // a node + // type: String + // a variable suffix for a class name + dojo.removeClass(node, "dojoDndItem" + type); + }, + _getChildByEvent: function(e){ + // summary: + // gets a child, which is under the mouse at the moment, or null + // e: Event + // a mouse event + var node = e.target; + if(node){ + for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){ + if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; } + } + } + return null; + }, + _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){ + // summary: + // adds all necessary data to the output of the user-supplied creator function + var t = (this.creator || this.defaultCreator).call(this, item, hint); + if(!dojo.isArray(t.type)){ t.type = ["text"]; } + if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); } + dojo.addClass(t.node, "dojoDndItem"); + return t; + } +}); + +dojo.dnd._createNode = function(tag){ + // summary: + // returns a function, which creates an element of given tag + // (SPAN by default) and sets its innerHTML to given text + // tag: String + // a tag name or empty for SPAN + if(!tag){ return dojo.dnd._createSpan; } + return function(text){ // Function + return dojo.create(tag, {innerHTML: text}); // Node + }; +}; + +dojo.dnd._createTrTd = function(text){ + // summary: + // creates a TR/TD structure with given text as an innerHTML of TD + // text: String + // a text for TD + var tr = dojo.create("tr"); + dojo.create("td", {innerHTML: text}, tr); + return tr; // Node +}; + +dojo.dnd._createSpan = function(text){ + // summary: + // creates a SPAN element with given text as its innerHTML + // text: String + // a text for SPAN + return dojo.create("span", {innerHTML: text}); // Node +}; + +// dojo.dnd._defaultCreatorNodes: Object +// a dictionary that maps container tag names to child tag names +dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"}; + +dojo.dnd._defaultCreator = function(node){ + // summary: + // takes a parent node, and returns an appropriate creator function + // node: Node + // a container node + var tag = node.tagName.toLowerCase(); + var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd : + dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]); + return function(item, hint){ // Function + var isObj = item && dojo.isObject(item), data, type, n; + if(isObj && item.tagName && item.nodeType && item.getAttribute){ + // process a DOM node + data = item.getAttribute("dndData") || item.innerHTML; + type = item.getAttribute("dndType"); + type = type ? type.split(/\s*,\s*/) : ["text"]; + n = item; // this node is going to be moved rather than copied + }else{ + // process a DnD item object or a string + data = (isObj && item.data) ? item.data : item; + type = (isObj && item.type) ? item.type : ["text"]; + n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data)); + } + if(!n.id){ + n.id = dojo.dnd.getUniqueId(); + } + return {node: n, data: data, type: type}; + }; +}; + +} + +if(!dojo._hasResource["dijit.tree._dndContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.tree._dndContainer"] = true; +dojo.provide("dijit.tree._dndContainer"); + + + +dojo.declare("dijit.tree._dndContainer", + null, + { + + // summary: + // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly. + // It's modeled after `dojo.dnd.Container`. + // tags: + // protected + + /*===== + // current: DomNode + // The currently hovered TreeNode.rowNode (which is the DOM node + // associated w/a given node in the tree, excluding it's descendants) + current: null, + =====*/ + + constructor: function(tree, params){ + // summary: + // A constructor of the Container + // tree: Node + // Node or node's id to build the container on + // params: dijit.tree.__SourceArgs + // A dict of parameters, which gets mixed into the object + // tags: + // private + this.tree = tree; + this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree + dojo.mixin(this, params); + + // class-specific variables + this.map = {}; + this.current = null; // current TreeNode's DOM node + + // states + this.containerState = ""; + dojo.addClass(this.node, "dojoDndContainer"); + + // set up events + this.events = [ + // container level events + dojo.connect(this.node, "onmouseenter", this, "onOverEvent"), + dojo.connect(this.node, "onmouseleave", this, "onOutEvent"), + + // switching between TreeNodes + dojo.connect(this.tree, "_onNodeMouseEnter", this, "onMouseOver"), + dojo.connect(this.tree, "_onNodeMouseLeave", this, "onMouseOut"), + + // cancel text selection and text dragging + dojo.connect(this.node, "ondragstart", dojo, "stopEvent"), + dojo.connect(this.node, "onselectstart", dojo, "stopEvent") + ]; + }, + + getItem: function(/*String*/ key){ + // summary: + // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id). + // Called by dojo.dnd.Source.checkAcceptance(). + // tags: + // protected + + var node = this.selection[key], + ret = { + data: dijit.getEnclosingWidget(node), + type: ["treeNode"] + }; + + return ret; // dojo.dnd.Item + }, + + destroy: function(){ + // summary: + // Prepares this object to be garbage-collected + + dojo.forEach(this.events, dojo.disconnect); + // this.clearItems(); + this.node = this.parent = null; + }, + + // mouse events + onMouseOver: function(/*TreeNode*/ widget, /*Event*/ evt){ + // summary: + // Called when mouse is moved over a TreeNode + // tags: + // protected + this.current = widget.rowNode; + this.currentWidget = widget; + }, + + onMouseOut: function(/*TreeNode*/ widget, /*Event*/ evt){ + // summary: + // Called when mouse is moved away from a TreeNode + // tags: + // protected + this.current = null; + this.currentWidget = null; + }, + + _changeState: function(type, newState){ + // summary: + // Changes a named state to new state value + // type: String + // A name of the state to change + // newState: String + // new state + var prefix = "dojoDnd" + type; + var state = type.toLowerCase() + "State"; + //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]); + dojo.removeClass(this.node, prefix + this[state]); + dojo.addClass(this.node, prefix + newState); + this[state] = newState; + }, + + _addItemClass: function(node, type){ + // summary: + // Adds a class with prefix "dojoDndItem" + // node: Node + // A node + // type: String + // A variable suffix for a class name + dojo.addClass(node, "dojoDndItem" + type); + }, + + _removeItemClass: function(node, type){ + // summary: + // Removes a class with prefix "dojoDndItem" + // node: Node + // A node + // type: String + // A variable suffix for a class name + dojo.removeClass(node, "dojoDndItem" + type); + }, + + onOverEvent: function(){ + // summary: + // This function is called once, when mouse is over our container + // tags: + // protected + this._changeState("Container", "Over"); + }, + + onOutEvent: function(){ + // summary: + // This function is called once, when mouse is out of our container + // tags: + // protected + this._changeState("Container", ""); + } +}); + +} + +if(!dojo._hasResource["dijit.tree._dndSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.tree._dndSelector"] = true; +dojo.provide("dijit.tree._dndSelector"); + + + +dojo.declare("dijit.tree._dndSelector", + dijit.tree._dndContainer, + { + // summary: + // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly. + // It's based on `dojo.dnd.Selector`. + // tags: + // protected + + /*===== + // selection: Hash<String, DomNode> + // (id, DomNode) map for every TreeNode that's currently selected. + // The DOMNode is the TreeNode.rowNode. + selection: {}, + =====*/ + + constructor: function(tree, params){ + // summary: + // Initialization + // tags: + // private + + this.selection={}; + this.anchor = null; + this.simpleSelection=false; + + this.events.push( + dojo.connect(this.tree.domNode, "onmousedown", this,"onMouseDown"), + dojo.connect(this.tree.domNode, "onmouseup", this,"onMouseUp"), + dojo.connect(this.tree.domNode, "onmousemove", this,"onMouseMove") + ); + }, + + // singular: Boolean + // Allows selection of only one element, if true. + // Tree hasn't been tested in singular=true mode, unclear if it works. + singular: false, + + // methods + + getSelectedNodes: function(){ + // summary: + // Returns the set of selected nodes. + // Used by dndSource on the start of a drag. + // tags: + // protected + return this.selection; + }, + + selectNone: function(){ + // summary: + // Unselects all items + // tags: + // private + + return this._removeSelection()._removeAnchor(); // self + }, + + destroy: function(){ + // summary: + // Prepares the object to be garbage-collected + this.inherited(arguments); + this.selection = this.anchor = null; + }, + + // mouse events + onMouseDown: function(e){ + // summary: + // Event processor for onmousedown + // e: Event + // mouse event + // tags: + // protected + + if(!this.current){ return; } + + if(e.button == dojo.mouseButtons.RIGHT){ return; } // ignore right-click + + var treeNode = dijit.getEnclosingWidget(this.current), + id = treeNode.id + "-dnd" // so id doesn't conflict w/widget + + if(!dojo.hasAttr(this.current, "id")){ + dojo.attr(this.current, "id", id); + } + + if(!this.singular && !dojo.isCopyKey(e) && !e.shiftKey && (this.current.id in this.selection)){ + this.simpleSelection = true; + dojo.stopEvent(e); + return; + } + if(this.singular){ + if(this.anchor == this.current){ + if(dojo.isCopyKey(e)){ + this.selectNone(); + } + }else{ + this.selectNone(); + this.anchor = this.current; + this._addItemClass(this.anchor, "Anchor"); + + this.selection[this.current.id] = this.current; + } + }else{ + if(!this.singular && e.shiftKey){ + if(dojo.isCopyKey(e)){ + //TODO add range to selection + }else{ + //TODO select new range from anchor + } + }else{ + if(dojo.isCopyKey(e)){ + if(this.anchor == this.current){ + delete this.selection[this.anchor.id]; + this._removeAnchor(); + }else{ + if(this.current.id in this.selection){ + this._removeItemClass(this.current, "Selected"); + delete this.selection[this.current.id]; + }else{ + if(this.anchor){ + this._removeItemClass(this.anchor, "Anchor"); + this._addItemClass(this.anchor, "Selected"); + } + this.anchor = this.current; + this._addItemClass(this.current, "Anchor"); + this.selection[this.current.id] = this.current; + } + } + }else{ + if(!(id in this.selection)){ + this.selectNone(); + this.anchor = this.current; + this._addItemClass(this.current, "Anchor"); + this.selection[id] = this.current; + } + } + } + } + + dojo.stopEvent(e); + }, + + onMouseUp: function(e){ + // summary: + // Event processor for onmouseup + // e: Event + // mouse event + // tags: + // protected + + // TODO: this code is apparently for handling an edge case when the user is selecting + // multiple nodes and then mousedowns on a node by accident... it lets the user keep the + // current selection by moving the mouse away (or something like that). It doesn't seem + // to work though and requires a lot of plumbing (including this code, the onmousemove + // handler, and the this.simpleSelection attribute. Consider getting rid of all of it. + + if(!this.simpleSelection){ return; } + this.simpleSelection = false; + this.selectNone(); + if(this.current){ + this.anchor = this.current; + this._addItemClass(this.anchor, "Anchor"); + this.selection[this.current.id] = this.current; + } + }, + onMouseMove: function(e){ + // summary + // event processor for onmousemove + // e: Event + // mouse event + this.simpleSelection = false; + }, + + _removeSelection: function(){ + // summary: + // Unselects all items + // tags: + // private + var e = dojo.dnd._empty; + for(var i in this.selection){ + if(i in e){ continue; } + var node = dojo.byId(i); + if(node){ this._removeItemClass(node, "Selected"); } + } + this.selection = {}; + return this; // self + }, + + _removeAnchor: function(){ + // summary: + // Removes the Anchor CSS class from a node. + // According to `dojo.dnd.Selector`, anchor means that + // "an item is selected, and is an anchor for a 'shift' selection". + // It's not relevant for Tree at this point, since we don't support multiple selection. + // tags: + // private + if(this.anchor){ + this._removeItemClass(this.anchor, "Anchor"); + this.anchor = null; + } + return this; // self + }, + + forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){ + // summary: + // Iterates over selected items; + // see `dojo.dnd.Container.forInItems()` for details + o = o || dojo.global; + for(var id in this.selection){ + console.log("selected item id: " + id); + f.call(o, this.getItem(id), id, this); + } + } +}); + +} + +if(!dojo._hasResource["dojo.dnd.Avatar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.Avatar"] = true; +dojo.provide("dojo.dnd.Avatar"); + + + +dojo.declare("dojo.dnd.Avatar", null, { + // summary: + // Object that represents transferred DnD items visually + // manager: Object + // a DnD manager object + + constructor: function(manager){ + this.manager = manager; + this.construct(); + }, + + // methods + construct: function(){ + // summary: + // constructor function; + // it is separate so it can be (dynamically) overwritten in case of need + this.isA11y = dojo.hasClass(dojo.body(),"dijit_a11y"); + var a = dojo.create("table", { + "class": "dojoDndAvatar", + style: { + position: "absolute", + zIndex: "1999", + margin: "0px" + } + }), + source = this.manager.source, node, + b = dojo.create("tbody", null, a), + tr = dojo.create("tr", null, b), + td = dojo.create("td", null, tr), + icon = this.isA11y ? dojo.create("span", { + id : "a11yIcon", + innerHTML : this.manager.copy ? '+' : "<" + }, td) : null, + span = dojo.create("span", { + innerHTML: source.generateText ? this._generateText() : "" + }, td), + k = Math.min(5, this.manager.nodes.length), i = 0; + // we have to set the opacity on IE only after the node is live + dojo.attr(tr, { + "class": "dojoDndAvatarHeader", + style: {opacity: 0.9} + }); + for(; i < k; ++i){ + if(source.creator){ + // create an avatar representation of the node + node = source._normalizedCreator(source.getItem(this.manager.nodes[i].id).data, "avatar").node; + }else{ + // or just clone the node and hope it works + node = this.manager.nodes[i].cloneNode(true); + if(node.tagName.toLowerCase() == "tr"){ + // insert extra table nodes + var table = dojo.create("table"), + tbody = dojo.create("tbody", null, table); + tbody.appendChild(node); + node = table; + } + } + node.id = ""; + tr = dojo.create("tr", null, b); + td = dojo.create("td", null, tr); + td.appendChild(node); + dojo.attr(tr, { + "class": "dojoDndAvatarItem", + style: {opacity: (9 - i) / 10} + }); + } + this.node = a; + }, + destroy: function(){ + // summary: + // destructor for the avatar; called to remove all references so it can be garbage-collected + dojo.destroy(this.node); + this.node = false; + }, + update: function(){ + // summary: + // updates the avatar to reflect the current DnD state + dojo[(this.manager.canDropFlag ? "add" : "remove") + "Class"](this.node, "dojoDndAvatarCanDrop"); + if (this.isA11y){ + var icon = dojo.byId("a11yIcon"); + var text = '+'; // assume canDrop && copy + if (this.manager.canDropFlag && !this.manager.copy) { + text = '< '; // canDrop && move + }else if (!this.manager.canDropFlag && !this.manager.copy) { + text = "o"; //!canDrop && move + }else if(!this.manager.canDropFlag){ + text = 'x'; // !canDrop && copy + } + icon.innerHTML=text; + } + // replace text + dojo.query(("tr.dojoDndAvatarHeader td span" +(this.isA11y ? " span" : "")), this.node).forEach( + function(node){ + node.innerHTML = this._generateText(); + }, this); + }, + _generateText: function(){ + // summary: generates a proper text to reflect copying or moving of items + return this.manager.nodes.length.toString(); + } +}); + +} + +if(!dojo._hasResource["dojo.dnd.Manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.dnd.Manager"] = true; +dojo.provide("dojo.dnd.Manager"); + + + + + +dojo.declare("dojo.dnd.Manager", null, { + // summary: + // the manager of DnD operations (usually a singleton) + constructor: function(){ + this.avatar = null; + this.source = null; + this.nodes = []; + this.copy = true; + this.target = null; + this.canDropFlag = false; + this.events = []; + }, + + // avatar's offset from the mouse + OFFSET_X: 16, + OFFSET_Y: 16, + + // methods + overSource: function(source){ + // summary: + // called when a source detected a mouse-over condition + // source: Object + // the reporter + if(this.avatar){ + this.target = (source && source.targetState != "Disabled") ? source : null; + this.canDropFlag = Boolean(this.target); + this.avatar.update(); + } + dojo.publish("/dnd/source/over", [source]); + }, + outSource: function(source){ + // summary: + // called when a source detected a mouse-out condition + // source: Object + // the reporter + if(this.avatar){ + if(this.target == source){ + this.target = null; + this.canDropFlag = false; + this.avatar.update(); + dojo.publish("/dnd/source/over", [null]); + } + }else{ + dojo.publish("/dnd/source/over", [null]); + } + }, + startDrag: function(source, nodes, copy){ + // summary: + // called to initiate the DnD operation + // source: Object + // the source which provides items + // nodes: Array + // the list of transferred items + // copy: Boolean + // copy items, if true, move items otherwise + this.source = source; + this.nodes = nodes; + this.copy = Boolean(copy); // normalizing to true boolean + this.avatar = this.makeAvatar(); + dojo.body().appendChild(this.avatar.node); + dojo.publish("/dnd/start", [source, nodes, this.copy]); + this.events = [ + dojo.connect(dojo.doc, "onmousemove", this, "onMouseMove"), + dojo.connect(dojo.doc, "onmouseup", this, "onMouseUp"), + dojo.connect(dojo.doc, "onkeydown", this, "onKeyDown"), + dojo.connect(dojo.doc, "onkeyup", this, "onKeyUp"), + // cancel text selection and text dragging + dojo.connect(dojo.doc, "ondragstart", dojo.stopEvent), + dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent) + ]; + var c = "dojoDnd" + (copy ? "Copy" : "Move"); + dojo.addClass(dojo.body(), c); + }, + canDrop: function(flag){ + // summary: + // called to notify if the current target can accept items + var canDropFlag = Boolean(this.target && flag); + if(this.canDropFlag != canDropFlag){ + this.canDropFlag = canDropFlag; + this.avatar.update(); + } + }, + stopDrag: function(){ + // summary: + // stop the DnD in progress + dojo.removeClass(dojo.body(), "dojoDndCopy"); + dojo.removeClass(dojo.body(), "dojoDndMove"); + dojo.forEach(this.events, dojo.disconnect); + this.events = []; + this.avatar.destroy(); + this.avatar = null; + this.source = this.target = null; + this.nodes = []; + }, + makeAvatar: function(){ + // summary: + // makes the avatar; it is separate to be overwritten dynamically, if needed + return new dojo.dnd.Avatar(this); + }, + updateAvatar: function(){ + // summary: + // updates the avatar; it is separate to be overwritten dynamically, if needed + this.avatar.update(); + }, + + // mouse event processors + onMouseMove: function(e){ + // summary: + // event processor for onmousemove + // e: Event + // mouse event + var a = this.avatar; + if(a){ + dojo.dnd.autoScrollNodes(e); + //dojo.dnd.autoScroll(e); + var s = a.node.style; + s.left = (e.pageX + this.OFFSET_X) + "px"; + s.top = (e.pageY + this.OFFSET_Y) + "px"; + var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))); + if(this.copy != copy){ + this._setCopyStatus(copy); + } + } + }, + onMouseUp: function(e){ + // summary: + // event processor for onmouseup + // e: Event + // mouse event + if(this.avatar){ + if(this.target && this.canDropFlag){ + var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))), + params = [this.source, this.nodes, copy, this.target, e]; + dojo.publish("/dnd/drop/before", params); + dojo.publish("/dnd/drop", params); + }else{ + dojo.publish("/dnd/cancel"); + } + this.stopDrag(); + } + }, + + // keyboard event processors + onKeyDown: function(e){ + // summary: + // event processor for onkeydown: + // watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag + // e: Event + // keyboard event + if(this.avatar){ + switch(e.keyCode){ + case dojo.keys.CTRL: + var copy = Boolean(this.source.copyState(true)); + if(this.copy != copy){ + this._setCopyStatus(copy); + } + break; + case dojo.keys.ESCAPE: + dojo.publish("/dnd/cancel"); + this.stopDrag(); + break; + } + } + }, + onKeyUp: function(e){ + // summary: + // event processor for onkeyup, watching for CTRL for copy/move status + // e: Event + // keyboard event + if(this.avatar && e.keyCode == dojo.keys.CTRL){ + var copy = Boolean(this.source.copyState(false)); + if(this.copy != copy){ + this._setCopyStatus(copy); + } + } + }, + + // utilities + _setCopyStatus: function(copy){ + // summary: + // changes the copy status + // copy: Boolean + // the copy status + this.copy = copy; + this.source._markDndStatus(this.copy); + this.updateAvatar(); + dojo.removeClass(dojo.body(), "dojoDnd" + (this.copy ? "Move" : "Copy")); + dojo.addClass(dojo.body(), "dojoDnd" + (this.copy ? "Copy" : "Move")); + } +}); + +// dojo.dnd._manager: +// The manager singleton variable. Can be overwritten if needed. +dojo.dnd._manager = null; + +dojo.dnd.manager = function(){ + // summary: + // Returns the current DnD manager. Creates one if it is not created yet. + if(!dojo.dnd._manager){ + dojo.dnd._manager = new dojo.dnd.Manager(); + } + return dojo.dnd._manager; // Object +}; + +} + +if(!dojo._hasResource["dijit.tree.dndSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.tree.dndSource"] = true; +dojo.provide("dijit.tree.dndSource"); + + + + +/*===== +dijit.tree.__SourceArgs = function(){ + // summary: + // A dict of parameters for Tree source configuration. + // isSource: Boolean? + // Can be used as a DnD source. Defaults to true. + // accept: String[] + // List of accepted types (text strings) for a target; defaults to + // ["text", "treeNode"] + // copyOnly: Boolean? + // Copy items, if true, use a state of Ctrl key otherwise, + // dragThreshold: Number + // The move delay in pixels before detecting a drag; 0 by default + // betweenThreshold: Integer + // Distance from upper/lower edge of node to allow drop to reorder nodes + this.isSource = isSource; + this.accept = accept; + this.autoSync = autoSync; + this.copyOnly = copyOnly; + this.dragThreshold = dragThreshold; + this.betweenThreshold = betweenThreshold; +} +=====*/ + +dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, { + // summary: + // Handles drag and drop operations (as a source or a target) for `dijit.Tree` + + // isSource: [private] Boolean + // Can be used as a DnD source. + isSource: true, + + // accept: String[] + // List of accepted types (text strings) for the Tree; defaults to + // ["text"] + accept: ["text", "treeNode"], + + // copyOnly: [private] Boolean + // Copy items, if true, use a state of Ctrl key otherwise + copyOnly: false, + + // dragThreshold: Number + // The move delay in pixels before detecting a drag; 5 by default + dragThreshold: 5, + + // betweenThreshold: Integer + // Distance from upper/lower edge of node to allow drop to reorder nodes + betweenThreshold: 0, + + constructor: function(/*dijit.Tree*/ tree, /*dijit.tree.__SourceArgs*/ params){ + // summary: + // a constructor of the Tree DnD Source + // tags: + // private + if(!params){ params = {}; } + dojo.mixin(this, params); + this.isSource = typeof params.isSource == "undefined" ? true : params.isSource; + var type = params.accept instanceof Array ? params.accept : ["text", "treeNode"]; + this.accept = null; + if(type.length){ + this.accept = {}; + for(var i = 0; i < type.length; ++i){ + this.accept[type[i]] = 1; + } + } + + // class-specific variables + this.isDragging = false; + this.mouseDown = false; + this.targetAnchor = null; // DOMNode corresponding to the currently moused over TreeNode + this.targetBox = null; // coordinates of this.targetAnchor + this.dropPosition = ""; // whether mouse is over/after/before this.targetAnchor + this._lastX = 0; + this._lastY = 0; + + // states + this.sourceState = ""; + if(this.isSource){ + dojo.addClass(this.node, "dojoDndSource"); + } + this.targetState = ""; + if(this.accept){ + dojo.addClass(this.node, "dojoDndTarget"); + } + + // set up events + this.topics = [ + dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"), + dojo.subscribe("/dnd/start", this, "onDndStart"), + dojo.subscribe("/dnd/drop", this, "onDndDrop"), + dojo.subscribe("/dnd/cancel", this, "onDndCancel") + ]; + }, + + // methods + checkAcceptance: function(source, nodes){ + // summary: + // Checks if the target can accept nodes from this source + // source: dijit.tree.dndSource + // The source which provides items + // nodes: DOMNode[] + // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if + // source is a dijit.Tree. + // tags: + // extension + return true; // Boolean + }, + + copyState: function(keyPressed){ + // summary: + // Returns true, if we need to copy items, false to move. + // It is separated to be overwritten dynamically, if needed. + // keyPressed: Boolean + // The "copy" control key was pressed + // tags: + // protected + return this.copyOnly || keyPressed; // Boolean + }, + destroy: function(){ + // summary: + // Prepares the object to be garbage-collected. + this.inherited("destroy",arguments); + dojo.forEach(this.topics, dojo.unsubscribe); + this.targetAnchor = null; + }, + + _onDragMouse: function(e){ + // summary: + // Helper method for processing onmousemove/onmouseover events while drag is in progress. + // Keeps track of current drop target. + + var m = dojo.dnd.manager(), + oldTarget = this.targetAnchor, // the DOMNode corresponding to TreeNode mouse was previously over + newTarget = this.current, // DOMNode corresponding to TreeNode mouse is currently over + newTargetWidget = this.currentWidget, // the TreeNode itself + oldDropPosition = this.dropPosition; // the previous drop position (over/before/after) + + // calculate if user is indicating to drop the dragged node before, after, or over + // (i.e., to become a child of) the target node + var newDropPosition = "Over"; + if(newTarget && this.betweenThreshold > 0){ + // If mouse is over a new TreeNode, then get new TreeNode's position and size + if(!this.targetBox || oldTarget != newTarget){ + this.targetBox = dojo.position(newTarget, true); + } + if((e.pageY - this.targetBox.y) <= this.betweenThreshold){ + newDropPosition = "Before"; + }else if((e.pageY - this.targetBox.y) >= (this.targetBox.h - this.betweenThreshold)){ + newDropPosition = "After"; + } + } + + if(newTarget != oldTarget || newDropPosition != oldDropPosition){ + if(oldTarget){ + this._removeItemClass(oldTarget, oldDropPosition); + } + if(newTarget){ + this._addItemClass(newTarget, newDropPosition); + } + + // Check if it's ok to drop the dragged node on/before/after the target node. + if(!newTarget){ + m.canDrop(false); + }else if(newTargetWidget == this.tree.rootNode && newDropPosition != "Over"){ + // Can't drop before or after tree's root node; the dropped node would just disappear (at least visually) + m.canDrop(false); + }else if(m.source == this && (newTarget.id in this.selection)){ + // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140) + m.canDrop(false); + }else if(this.checkItemAcceptance(newTarget, m.source, newDropPosition.toLowerCase()) + && !this._isParentChildDrop(m.source, newTarget)){ + m.canDrop(true); + }else{ + m.canDrop(false); + } + + this.targetAnchor = newTarget; + this.dropPosition = newDropPosition; + } + }, + + onMouseMove: function(e){ + // summary: + // Called for any onmousemove events over the Tree + // e: Event + // onmousemouse event + // tags: + // private + if(this.isDragging && this.targetState == "Disabled"){ return; } + this.inherited(arguments); + var m = dojo.dnd.manager(); + if(this.isDragging){ + this._onDragMouse(e); + }else{ + if(this.mouseDown && this.isSource && + (Math.abs(e.pageX-this._lastX)>=this.dragThreshold || Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){ + var n = this.getSelectedNodes(); + var nodes=[]; + for(var i in n){ + nodes.push(n[i]); + } + if(nodes.length){ + m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e))); + } + } + } + }, + + onMouseDown: function(e){ + // summary: + // Event processor for onmousedown + // e: Event + // onmousedown event + // tags: + // private + this.mouseDown = true; + this.mouseButton = e.button; + this._lastX = e.pageX; + this._lastY = e.pageY; + this.inherited("onMouseDown",arguments); + }, + + onMouseUp: function(e){ + // summary: + // Event processor for onmouseup + // e: Event + // onmouseup event + // tags: + // private + if(this.mouseDown){ + this.mouseDown = false; + this.inherited("onMouseUp",arguments); + } + }, + + onMouseOut: function(){ + // summary: + // Event processor for when mouse is moved away from a TreeNode + // tags: + // private + this.inherited(arguments); + this._unmarkTargetAnchor(); + }, + + checkItemAcceptance: function(target, source, position){ + // summary: + // Stub function to be overridden if one wants to check for the ability to drop at the node/item level + // description: + // In the base case, this is called to check if target can become a child of source. + // When betweenThreshold is set, position="before" or "after" means that we + // are asking if the source node can be dropped before/after the target node. + // target: DOMNode + // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to + // Use dijit.getEnclosingWidget(target) to get the TreeNode. + // source: dijit.tree.dndSource + // The (set of) nodes we are dropping + // position: String + // "over", "before", or "after" + // tags: + // extension + return true; + }, + + // topic event processors + onDndSourceOver: function(source){ + // summary: + // Topic event processor for /dnd/source/over, called when detected a current source. + // source: Object + // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it + // tags: + // private + if(this != source){ + this.mouseDown = false; + this._unmarkTargetAnchor(); + }else if(this.isDragging){ + var m = dojo.dnd.manager(); + m.canDrop(false); + } + }, + onDndStart: function(source, nodes, copy){ + // summary: + // Topic event processor for /dnd/start, called to initiate the DnD operation + // source: Object + // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items + // nodes: DomNode[] + // The list of transferred items, dndTreeNode nodes if dragging from a Tree + // copy: Boolean + // Copy items, if true, move items otherwise + // tags: + // private + + if(this.isSource){ + this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : ""); + } + var accepted = this.checkAcceptance(source, nodes); + + this._changeState("Target", accepted ? "" : "Disabled"); + + if(this == source){ + dojo.dnd.manager().overSource(this); + } + + this.isDragging = true; + }, + + itemCreator: function(/*DomNode[]*/ nodes, target, /*dojo.dnd.Source*/ source){ + // summary: + // Returns objects passed to `Tree.model.newItem()` based on DnD nodes + // dropped onto the tree. Developer must override this method to enable + // dropping from external sources onto this Tree, unless the Tree.model's items + // happen to look like {id: 123, name: "Apple" } with no other attributes. + // description: + // For each node in nodes[], which came from source, create a hash of name/value + // pairs to be passed to Tree.model.newItem(). Returns array of those hashes. + // returns: Object[] + // Array of name/value hashes for each new item to be added to the Tree, like: + // | [ + // | { id: 123, label: "apple", foo: "bar" }, + // | { id: 456, label: "pear", zaz: "bam" } + // | ] + // tags: + // extension + + // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and + // make signature itemCreator(sourceItem, node, target) (or similar). + + return dojo.map(nodes, function(node){ + return { + "id": node.id, + "name": node.textContent || node.innerText || "" + }; + }); // Object[] + }, + + onDndDrop: function(source, nodes, copy){ + // summary: + // Topic event processor for /dnd/drop, called to finish the DnD operation. + // description: + // Updates data store items according to where node was dragged from and dropped + // to. The tree will then respond to those data store updates and redraw itself. + // source: Object + // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items + // nodes: DomNode[] + // The list of transferred items, dndTreeNode nodes if dragging from a Tree + // copy: Boolean + // Copy items, if true, move items otherwise + // tags: + // protected + if(this.containerState == "Over"){ + var tree = this.tree, + model = tree.model, + target = this.targetAnchor, + requeryRoot = false; // set to true iff top level items change + + this.isDragging = false; + + // Compute the new parent item + var targetWidget = dijit.getEnclosingWidget(target); + var newParentItem; + var insertIndex; + newParentItem = (targetWidget && targetWidget.item) || tree.item; + if(this.dropPosition == "Before" || this.dropPosition == "After"){ + // TODO: if there is no parent item then disallow the drop. + // Actually this should be checked during onMouseMove too, to make the drag icon red. + newParentItem = (targetWidget.getParent() && targetWidget.getParent().item) || tree.item; + // Compute the insert index for reordering + insertIndex = targetWidget.getIndexInParent(); + if(this.dropPosition == "After"){ + insertIndex = targetWidget.getIndexInParent() + 1; + } + }else{ + newParentItem = (targetWidget && targetWidget.item) || tree.item; + } + + // If necessary, use this variable to hold array of hashes to pass to model.newItem() + // (one entry in the array for each dragged node). + var newItemsParams; + + dojo.forEach(nodes, function(node, idx){ + // dojo.dnd.Item representing the thing being dropped. + // Don't confuse the use of item here (meaning a DnD item) with the + // uses below where item means dojo.data item. + var sourceItem = source.getItem(node.id); + + // Information that's available if the source is another Tree + // (possibly but not necessarily this tree, possibly but not + // necessarily the same model as this Tree) + if(dojo.indexOf(sourceItem.type, "treeNode") != -1){ + var childTreeNode = sourceItem.data, + childItem = childTreeNode.item, + oldParentItem = childTreeNode.getParent().item; + } + + if(source == this){ + // This is a node from my own tree, and we are moving it, not copying. + // Remove item from old parent's children attribute. + // TODO: dijit.tree.dndSelector should implement deleteSelectedNodes() + // and this code should go there. + + if(typeof insertIndex == "number"){ + if(newParentItem == oldParentItem && childTreeNode.getIndexInParent() < insertIndex){ + insertIndex -= 1; + } + } + model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex); + }else if(model.isItem(childItem)){ + // Item from same model + // (maybe we should only do this branch if the source is a tree?) + model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex); + }else{ + // Get the hash to pass to model.newItem(). A single call to + // itemCreator() returns an array of hashes, one for each drag source node. + if(!newItemsParams){ + newItemsParams = this.itemCreator(nodes, target, source); + } + + // Create new item in the tree, based on the drag source. + model.newItem(newItemsParams[idx], newParentItem, insertIndex); + } + }, this); + + // Expand the target node (if it's currently collapsed) so the user can see + // where their node was dropped. In particular since that node is still selected. + this.tree._expandNode(targetWidget); + } + this.onDndCancel(); + }, + + onDndCancel: function(){ + // summary: + // Topic event processor for /dnd/cancel, called to cancel the DnD operation + // tags: + // private + this._unmarkTargetAnchor(); + this.isDragging = false; + this.mouseDown = false; + delete this.mouseButton; + this._changeState("Source", ""); + this._changeState("Target", ""); + }, + + // When focus moves in/out of the entire Tree + onOverEvent: function(){ + // summary: + // This method is called when mouse is moved over our container (like onmouseenter) + // tags: + // private + this.inherited(arguments); + dojo.dnd.manager().overSource(this); + }, + onOutEvent: function(){ + // summary: + // This method is called when mouse is moved out of our container (like onmouseleave) + // tags: + // private + this._unmarkTargetAnchor(); + var m = dojo.dnd.manager(); + if(this.isDragging){ + m.canDrop(false); + } + m.outSource(this); + + this.inherited(arguments); + }, + + _isParentChildDrop: function(source, targetRow){ + // summary: + // Checks whether the dragged items are parent rows in the tree which are being + // dragged into their own children. + // + // source: + // The DragSource object. + // + // targetRow: + // The tree row onto which the dragged nodes are being dropped. + // + // tags: + // private + + // If the dragged object is not coming from the tree this widget belongs to, + // it cannot be invalid. + if(!source.tree || source.tree != this.tree){ + return false; + } + + + var root = source.tree.domNode; + var ids = {}; + for(var x in source.selection){ + ids[source.selection[x].parentNode.id] = true; + } + + var node = targetRow.parentNode; + + // Iterate up the DOM hierarchy from the target drop row, + // checking of any of the dragged nodes have the same ID. + while(node != root && (!node.id || !ids[node.id])){ + node = node.parentNode; + } + + return node.id && ids[node.id]; + }, + + _unmarkTargetAnchor: function(){ + // summary: + // Removes hover class of the current target anchor + // tags: + // private + if(!this.targetAnchor){ return; } + this._removeItemClass(this.targetAnchor, this.dropPosition); + this.targetAnchor = null; + this.targetBox = null; + this.dropPosition = null; + }, + + _markDndStatus: function(copy){ + // summary: + // Changes source's state based on "copy" status + this._changeState("Source", copy ? "Copied" : "Moved"); + } +}); + +} + +if(!dojo._hasResource["dojo.data.ItemFileReadStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.data.ItemFileReadStore"] = true; +dojo.provide("dojo.data.ItemFileReadStore"); + + + + + +dojo.declare("dojo.data.ItemFileReadStore", null,{ + // summary: + // The ItemFileReadStore implements the dojo.data.api.Read API and reads + // data from JSON files that have contents in this format -- + // { items: [ + // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]}, + // { name:'Fozzie Bear', wears:['hat', 'tie']}, + // { name:'Miss Piggy', pets:'Foo-Foo'} + // ]} + // Note that it can also contain an 'identifer' property that specified which attribute on the items + // in the array of items that acts as the unique identifier for that item. + // + constructor: function(/* Object */ keywordParameters){ + // summary: constructor + // keywordParameters: {url: String} + // keywordParameters: {data: jsonObject} + // keywordParameters: {typeMap: object) + // The structure of the typeMap object is as follows: + // { + // type0: function || object, + // type1: function || object, + // ... + // typeN: function || object + // } + // Where if it is a function, it is assumed to be an object constructor that takes the + // value of _value as the initialization parameters. If it is an object, then it is assumed + // to be an object of general form: + // { + // type: function, //constructor. + // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. + // } + + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = []; + this._loadFinished = false; + this._jsonFileUrl = keywordParameters.url; + this._ccUrl = keywordParameters.url; + this.url = keywordParameters.url; + this._jsonData = keywordParameters.data; + this.data = null; + this._datatypeMap = keywordParameters.typeMap || {}; + if(!this._datatypeMap['Date']){ + //If no default mapping for dates, then set this as default. + //We use the dojo.date.stamp here because the ISO format is the 'dojo way' + //of generically representing dates. + this._datatypeMap['Date'] = { + type: Date, + deserialize: function(value){ + return dojo.date.stamp.fromISOString(value); + } + }; + } + this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true}; + this._itemsByIdentity = null; + this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item. + this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item. + this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item. + this._reverseRefMap = "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity + this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset. + this._queuedFetches = []; + if(keywordParameters.urlPreventCache !== undefined){ + this.urlPreventCache = keywordParameters.urlPreventCache?true:false; + } + if(keywordParameters.hierarchical !== undefined){ + this.hierarchical = keywordParameters.hierarchical?true:false; + } + if(keywordParameters.clearOnClose){ + this.clearOnClose = true; + } + if("failOk" in keywordParameters){ + this.failOk = keywordParameters.failOk?true:false; + } + }, + + url: "", // use "" rather than undefined for the benefit of the parser (#3539) + + //Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload + //when clearOnClose and close is used. + _ccUrl: "", + + data: null, // define this so that the parser can populate it + + typeMap: null, //Define so parser can populate. + + //Parameter to allow users to specify if a close call should force a reload or not. + //By default, it retains the old behavior of not clearing if close is called. But + //if set true, the store will be reset to default state. Note that by doing this, + //all item handles will become invalid and a new fetch must be issued. + clearOnClose: false, + + //Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url. + //Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option. + //Added for tracker: #6072 + urlPreventCache: false, + + //Parameter for specifying that it is OK for the xhrGet call to fail silently. + failOk: false, + + //Parameter to indicate to process data from the url as hierarchical + //(data items can contain other data items in js form). Default is true + //for backwards compatibility. False means only root items are processed + //as items, all child objects outside of type-mapped objects and those in + //specific reference format, are left straight JS data objects. + hierarchical: true, + + _assertIsItem: function(/* item */ item){ + // summary: + // This function tests whether the item passed in is indeed an item in the store. + // item: + // The item to test for being contained by the store. + if(!this.isItem(item)){ + throw new Error("dojo.data.ItemFileReadStore: Invalid item argument."); + } + }, + + _assertIsAttribute: function(/* attribute-name-string */ attribute){ + // summary: + // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. + // attribute: + // The attribute to test for being contained by the store. + if(typeof attribute !== "string"){ + throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument."); + } + }, + + getValue: function( /* item */ item, + /* attribute-name-string */ attribute, + /* value? */ defaultValue){ + // summary: + // See dojo.data.api.Read.getValue() + var values = this.getValues(item, attribute); + return (values.length > 0)?values[0]:defaultValue; // mixed + }, + + getValues: function(/* item */ item, + /* attribute-name-string */ attribute){ + // summary: + // See dojo.data.api.Read.getValues() + + this._assertIsItem(item); + this._assertIsAttribute(attribute); + // Clone it before returning. refs: #10474 + return (item[attribute] || []).slice(0); // Array + }, + + getAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getAttributes() + this._assertIsItem(item); + var attributes = []; + for(var key in item){ + // Save off only the real item attributes, not the special id marks for O(1) isItem. + if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName) && (key !== this._reverseRefMap)){ + attributes.push(key); + } + } + return attributes; // Array + }, + + hasAttribute: function( /* item */ item, + /* attribute-name-string */ attribute){ + // summary: + // See dojo.data.api.Read.hasAttribute() + this._assertIsItem(item); + this._assertIsAttribute(attribute); + return (attribute in item); + }, + + containsValue: function(/* item */ item, + /* attribute-name-string */ attribute, + /* anything */ value){ + // summary: + // See dojo.data.api.Read.containsValue() + var regexp = undefined; + if(typeof value === "string"){ + regexp = dojo.data.util.filter.patternToRegExp(value, false); + } + return this._containsValue(item, attribute, value, regexp); //boolean. + }, + + _containsValue: function( /* item */ item, + /* attribute-name-string */ attribute, + /* anything */ value, + /* RegExp?*/ regexp){ + // summary: + // Internal function for looking at the values contained by the item. + // description: + // Internal function for looking at the values contained by the item. This + // function allows for denoting if the comparison should be case sensitive for + // strings or not (for handling filtering cases where string case should not matter) + // + // item: + // The data item to examine for attribute values. + // attribute: + // The attribute to inspect. + // value: + // The value to match. + // regexp: + // Optional regular expression generated off value if value was of string type to handle wildcarding. + // If present and attribute values are string, then it can be used for comparison instead of 'value' + return dojo.some(this.getValues(item, attribute), function(possibleValue){ + if(possibleValue !== null && !dojo.isObject(possibleValue) && regexp){ + if(possibleValue.toString().match(regexp)){ + return true; // Boolean + } + }else if(value === possibleValue){ + return true; // Boolean + } + }); + }, + + isItem: function(/* anything */ something){ + // summary: + // See dojo.data.api.Read.isItem() + if(something && something[this._storeRefPropName] === this){ + if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){ + return true; + } + } + return false; // Boolean + }, + + isItemLoaded: function(/* anything */ something){ + // summary: + // See dojo.data.api.Read.isItemLoaded() + return this.isItem(something); //boolean + }, + + loadItem: function(/* object */ keywordArgs){ + // summary: + // See dojo.data.api.Read.loadItem() + this._assertIsItem(keywordArgs.item); + }, + + getFeatures: function(){ + // summary: + // See dojo.data.api.Read.getFeatures() + return this._features; //Object + }, + + getLabel: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabel() + if(this._labelAttr && this.isItem(item)){ + return this.getValue(item,this._labelAttr); //String + } + return undefined; //undefined + }, + + getLabelAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabelAttributes() + if(this._labelAttr){ + return [this._labelAttr]; //array + } + return null; //null + }, + + _fetchItems: function( /* Object */ keywordArgs, + /* Function */ findCallback, + /* Function */ errorCallback){ + // summary: + // See dojo.data.util.simpleFetch.fetch() + var self = this, + filter = function(requestArgs, arrayOfItems){ + var items = [], + i, key; + if(requestArgs.query){ + var value, + ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false; + + //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the + //same value for each item examined. Much more efficient. + var regexpList = {}; + for(key in requestArgs.query){ + value = requestArgs.query[key]; + if(typeof value === "string"){ + regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase); + }else if(value instanceof RegExp){ + regexpList[key] = value; + } + } + for(i = 0; i < arrayOfItems.length; ++i){ + var match = true; + var candidateItem = arrayOfItems[i]; + if(candidateItem === null){ + match = false; + }else{ + for(key in requestArgs.query){ + value = requestArgs.query[key]; + if(!self._containsValue(candidateItem, key, value, regexpList[key])){ + match = false; + } + } + } + if(match){ + items.push(candidateItem); + } + } + findCallback(items, requestArgs); + }else{ + // We want a copy to pass back in case the parent wishes to sort the array. + // We shouldn't allow resort of the internal list, so that multiple callers + // can get lists and sort without affecting each other. We also need to + // filter out any null values that have been left as a result of deleteItem() + // calls in ItemFileWriteStore. + for(i = 0; i < arrayOfItems.length; ++i){ + var item = arrayOfItems[i]; + if(item !== null){ + items.push(item); + } + } + findCallback(items, requestArgs); + } + }; + + if(this._loadFinished){ + filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); + }else{ + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + dojo.deprecated("dojo.data.ItemFileReadStore: ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } + + //See if there was any forced reset of data. + if(this.data != null && this._jsonData == null){ + this._jsonData = this.data; + this.data = null; + } + + if(this._jsonFileUrl){ + //If fetches come in before the loading has finished, but while + //a load is in progress, we have to defer the fetching to be + //invoked in the callback. + if(this._loadInProgress){ + this._queuedFetches.push({args: keywordArgs, filter: filter}); + }else{ + this._loadInProgress = true; + var getArgs = { + url: self._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk + }; + var getHandler = dojo.xhrGet(getArgs); + getHandler.addCallback(function(data){ + try{ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + self._loadInProgress = false; + + filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions)); + self._handleQueuedFetches(); + }catch(e){ + self._loadFinished = true; + self._loadInProgress = false; + errorCallback(e, keywordArgs); + } + }); + getHandler.addErrback(function(error){ + self._loadInProgress = false; + errorCallback(error, keywordArgs); + }); + + //Wire up the cancel to abort of the request + //This call cancel on the deferred if it hasn't been called + //yet and then will chain to the simple abort of the + //simpleFetch keywordArgs + var oldAbort = null; + if(keywordArgs.abort){ + oldAbort = keywordArgs.abort; + } + keywordArgs.abort = function(){ + var df = getHandler; + if(df && df.fired === -1){ + df.cancel(); + df = null; + } + if(oldAbort){ + oldAbort.call(keywordArgs); + } + }; + } + }else if(this._jsonData){ + try{ + this._loadFinished = true; + this._getItemsFromLoadedData(this._jsonData); + this._jsonData = null; + filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); + }catch(e){ + errorCallback(e, keywordArgs); + } + }else{ + errorCallback(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs); + } + } + }, + + _handleQueuedFetches: function(){ + // summary: + // Internal function to execute delayed request in the store. + //Execute any deferred fetches now. + if(this._queuedFetches.length > 0){ + for(var i = 0; i < this._queuedFetches.length; i++){ + var fData = this._queuedFetches[i], + delayedQuery = fData.args, + delayedFilter = fData.filter; + if(delayedFilter){ + delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions)); + }else{ + this.fetchItemByIdentity(delayedQuery); + } + } + this._queuedFetches = []; + } + }, + + _getItemsArray: function(/*object?*/queryOptions){ + // summary: + // Internal function to determine which list of items to search over. + // queryOptions: The query options parameter, if any. + if(queryOptions && queryOptions.deep){ + return this._arrayOfAllItems; + } + return this._arrayOfTopLevelItems; + }, + + close: function(/*dojo.data.api.Request || keywordArgs || null */ request){ + // summary: + // See dojo.data.api.Read.close() + if(this.clearOnClose && + this._loadFinished && + !this._loadInProgress){ + //Reset all internalsback to default state. This will force a reload + //on next fetch. This also checks that the data or url param was set + //so that the store knows it can get data. Without one of those being set, + //the next fetch will trigger an error. + + if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) && + (this.url == "" || this.url == null) + ) && this.data == null){ + console.debug("dojo.data.ItemFileReadStore: WARNING! Data reload " + + " information has not been provided." + + " Please set 'url' or 'data' to the appropriate value before" + + " the next fetch"); + } + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = []; + this._loadFinished = false; + this._itemsByIdentity = null; + this._loadInProgress = false; + this._queuedFetches = []; + } + }, + + _getItemsFromLoadedData: function(/* Object */ dataObject){ + // summary: + // Function to parse the loaded data into item format and build the internal items array. + // description: + // Function to parse the loaded data into item format and build the internal items array. + // + // dataObject: + // The JS data object containing the raw data to convery into item format. + // + // returns: array + // Array of items in store item format. + + // First, we define a couple little utility functions... + var addingArrays = false, + self = this; + + function valueIsAnItem(/* anything */ aValue){ + // summary: + // Given any sort of value that could be in the raw json data, + // return true if we should interpret the value as being an + // item itself, rather than a literal value or a reference. + // example: + // | false == valueIsAnItem("Kermit"); + // | false == valueIsAnItem(42); + // | false == valueIsAnItem(new Date()); + // | false == valueIsAnItem({_type:'Date', _value:'May 14, 1802'}); + // | false == valueIsAnItem({_reference:'Kermit'}); + // | true == valueIsAnItem({name:'Kermit', color:'green'}); + // | true == valueIsAnItem({iggy:'pop'}); + // | true == valueIsAnItem({foo:42}); + var isItem = ( + (aValue !== null) && + (typeof aValue === "object") && + (!dojo.isArray(aValue) || addingArrays) && + (!dojo.isFunction(aValue)) && + (aValue.constructor == Object || dojo.isArray(aValue)) && + (typeof aValue._reference === "undefined") && + (typeof aValue._type === "undefined") && + (typeof aValue._value === "undefined") && + self.hierarchical + ); + return isItem; + } + + function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){ + self._arrayOfAllItems.push(anItem); + for(var attribute in anItem){ + var valueForAttribute = anItem[attribute]; + if(valueForAttribute){ + if(dojo.isArray(valueForAttribute)){ + var valueArray = valueForAttribute; + for(var k = 0; k < valueArray.length; ++k){ + var singleValue = valueArray[k]; + if(valueIsAnItem(singleValue)){ + addItemAndSubItemsToArrayOfAllItems(singleValue); + } + } + }else{ + if(valueIsAnItem(valueForAttribute)){ + addItemAndSubItemsToArrayOfAllItems(valueForAttribute); + } + } + } + } + } + + this._labelAttr = dataObject.label; + + // We need to do some transformations to convert the data structure + // that we read from the file into a format that will be convenient + // to work with in memory. + + // Step 1: Walk through the object hierarchy and build a list of all items + var i, + item; + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = dataObject.items; + + for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){ + item = this._arrayOfTopLevelItems[i]; + if(dojo.isArray(item)){ + addingArrays = true; + } + addItemAndSubItemsToArrayOfAllItems(item); + item[this._rootItemPropName]=true; + } + + // Step 2: Walk through all the attribute values of all the items, + // and replace single values with arrays. For example, we change this: + // { name:'Miss Piggy', pets:'Foo-Foo'} + // into this: + // { name:['Miss Piggy'], pets:['Foo-Foo']} + // + // We also store the attribute names so we can validate our store + // reference and item id special properties for the O(1) isItem + var allAttributeNames = {}, + key; + + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + for(key in item){ + if(key !== this._rootItemPropName){ + var value = item[key]; + if(value !== null){ + if(!dojo.isArray(value)){ + item[key] = [value]; + } + }else{ + item[key] = [null]; + } + } + allAttributeNames[key]=key; + } + } + + // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName + // This should go really fast, it will generally never even run the loop. + while(allAttributeNames[this._storeRefPropName]){ + this._storeRefPropName += "_"; + } + while(allAttributeNames[this._itemNumPropName]){ + this._itemNumPropName += "_"; + } + while(allAttributeNames[this._reverseRefMap]){ + this._reverseRefMap += "_"; + } + + // Step 4: Some data files specify an optional 'identifier', which is + // the name of an attribute that holds the identity of each item. + // If this data file specified an identifier attribute, then build a + // hash table of items keyed by the identity of the items. + var arrayOfValues; + + var identifier = dataObject.identifier; + if(identifier){ + this._itemsByIdentity = {}; + this._features['dojo.data.api.Identity'] = identifier; + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + arrayOfValues = item[identifier]; + var identity = arrayOfValues[0]; + if(!this._itemsByIdentity[identity]){ + this._itemsByIdentity[identity] = item; + }else{ + if(this._jsonFileUrl){ + throw new Error("dojo.data.ItemFileReadStore: The json data as specified by: [" + this._jsonFileUrl + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); + }else if(this._jsonData){ + throw new Error("dojo.data.ItemFileReadStore: The json data provided by the creation arguments is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); + } + } + } + }else{ + this._features['dojo.data.api.Identity'] = Number; + } + + // Step 5: Walk through all the items, and set each item's properties + // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true. + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + item[this._storeRefPropName] = this; + item[this._itemNumPropName] = i; + } + + // Step 6: We walk through all the attribute values of all the items, + // looking for type/value literals and item-references. + // + // We replace item-references with pointers to items. For example, we change: + // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + // into this: + // { name:['Kermit'], friends:[miss_piggy] } + // (where miss_piggy is the object representing the 'Miss Piggy' item). + // + // We replace type/value pairs with typed-literals. For example, we change: + // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'July 18, 1918'}] } + // into this: + // { name:['Kermit'], born:(new Date('July 18, 1918')) } + // + // We also generate the associate map for all items for the O(1) isItem function. + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + for(key in item){ + arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}] + for(var j = 0; j < arrayOfValues.length; ++j){ + value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}} + if(value !== null && typeof value == "object"){ + if(("_type" in value) && ("_value" in value)){ + var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber' + var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}} + if(!mappingObj){ + throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'"); + }else if(dojo.isFunction(mappingObj)){ + arrayOfValues[j] = new mappingObj(value._value); + }else if(dojo.isFunction(mappingObj.deserialize)){ + arrayOfValues[j] = mappingObj.deserialize(value._value); + }else{ + throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function"); + } + } + if(value._reference){ + var referenceDescription = value._reference; // example: {name:'Miss Piggy'} + if(!dojo.isObject(referenceDescription)){ + // example: 'Miss Piggy' + // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]} + arrayOfValues[j] = this._getItemByIdentity(referenceDescription); + }else{ + // example: {name:'Miss Piggy'} + // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + for(var k = 0; k < this._arrayOfAllItems.length; ++k){ + var candidateItem = this._arrayOfAllItems[k], + found = true; + for(var refKey in referenceDescription){ + if(candidateItem[refKey] != referenceDescription[refKey]){ + found = false; + } + } + if(found){ + arrayOfValues[j] = candidateItem; + } + } + } + if(this.referenceIntegrity){ + var refItem = arrayOfValues[j]; + if(this.isItem(refItem)){ + this._addReferenceToMap(refItem, item, key); + } + } + }else if(this.isItem(value)){ + //It's a child item (not one referenced through _reference). + //We need to treat this as a referenced item, so it can be cleaned up + //in a write store easily. + if(this.referenceIntegrity){ + this._addReferenceToMap(value, item, key); + } + } + } + } + } + } + }, + + _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){ + // summary: + // Method to add an reference map entry for an item and attribute. + // description: + // Method to add an reference map entry for an item and attribute. // + // refItem: + // The item that is referenced. + // parentItem: + // The item that holds the new reference to refItem. + // attribute: + // The attribute on parentItem that contains the new reference. + + //Stub function, does nothing. Real processing is in ItemFileWriteStore. + }, + + getIdentity: function(/* item */ item){ + // summary: + // See dojo.data.api.Identity.getIdentity() + var identifier = this._features['dojo.data.api.Identity']; + if(identifier === Number){ + return item[this._itemNumPropName]; // Number + }else{ + var arrayOfValues = item[identifier]; + if(arrayOfValues){ + return arrayOfValues[0]; // Object || String + } + } + return null; // null + }, + + fetchItemByIdentity: function(/* Object */ keywordArgs){ + // summary: + // See dojo.data.api.Identity.fetchItemByIdentity() + + // Hasn't loaded yet, we have to trigger the load. + var item, + scope; + if(!this._loadFinished){ + var self = this; + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + dojo.deprecated("dojo.data.ItemFileReadStore: ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } + + //See if there was any forced reset of data. + if(this.data != null && this._jsonData == null){ + this._jsonData = this.data; + this.data = null; + } + + if(this._jsonFileUrl){ + + if(this._loadInProgress){ + this._queuedFetches.push({args: keywordArgs}); + }else{ + this._loadInProgress = true; + var getArgs = { + url: self._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk + }; + var getHandler = dojo.xhrGet(getArgs); + getHandler.addCallback(function(data){ + var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; + try{ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + self._loadInProgress = false; + item = self._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + keywordArgs.onItem.call(scope, item); + } + self._handleQueuedFetches(); + }catch(error){ + self._loadInProgress = false; + if(keywordArgs.onError){ + keywordArgs.onError.call(scope, error); + } + } + }); + getHandler.addErrback(function(error){ + self._loadInProgress = false; + if(keywordArgs.onError){ + var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; + keywordArgs.onError.call(scope, error); + } + }); + } + + }else if(this._jsonData){ + // Passed in data, no need to xhr. + self._getItemsFromLoadedData(self._jsonData); + self._jsonData = null; + self._loadFinished = true; + item = self._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + scope = keywordArgs.scope?keywordArgs.scope:dojo.global; + keywordArgs.onItem.call(scope, item); + } + } + }else{ + // Already loaded. We can just look it up and call back. + item = this._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + scope = keywordArgs.scope?keywordArgs.scope:dojo.global; + keywordArgs.onItem.call(scope, item); + } + } + }, + + _getItemByIdentity: function(/* Object */ identity){ + // summary: + // Internal function to look an item up by its identity map. + var item = null; + if(this._itemsByIdentity){ + item = this._itemsByIdentity[identity]; + }else{ + item = this._arrayOfAllItems[identity]; + } + if(item === undefined){ + item = null; + } + return item; // Object + }, + + getIdentityAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Identity.getIdentifierAttributes() + + var identifier = this._features['dojo.data.api.Identity']; + if(identifier === Number){ + // If (identifier === Number) it means getIdentity() just returns + // an integer item-number for each item. The dojo.data.api.Identity + // spec says we need to return null if the identity is not composed + // of attributes + return null; // null + }else{ + return [identifier]; // Array + } + }, + + _forceLoad: function(){ + // summary: + // Internal function to force a load of the store if it hasn't occurred yet. This is required + // for specific functions to work properly. + var self = this; + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + dojo.deprecated("dojo.data.ItemFileReadStore: ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } + + //See if there was any forced reset of data. + if(this.data != null && this._jsonData == null){ + this._jsonData = this.data; + this.data = null; + } + + if(this._jsonFileUrl){ + var getArgs = { + url: this._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk, + sync: true + }; + var getHandler = dojo.xhrGet(getArgs); + getHandler.addCallback(function(data){ + try{ + //Check to be sure there wasn't another load going on concurrently + //So we don't clobber data that comes in on it. If there is a load going on + //then do not save this data. It will potentially clobber current data. + //We mainly wanted to sync/wait here. + //TODO: Revisit the loading scheme of this store to improve multi-initial + //request handling. + if(self._loadInProgress !== true && !self._loadFinished){ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + }else if(self._loadInProgress){ + //Okay, we hit an error state we can't recover from. A forced load occurred + //while an async load was occurring. Since we cannot block at this point, the best + //that can be managed is to throw an error. + throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress."); + } + }catch(e){ + console.log(e); + throw e; + } + }); + getHandler.addErrback(function(error){ + throw error; + }); + }else if(this._jsonData){ + self._getItemsFromLoadedData(self._jsonData); + self._jsonData = null; + self._loadFinished = true; + } + } +}); +//Mix in the simple fetch implementation to this class. +dojo.extend(dojo.data.ItemFileReadStore,dojo.data.util.simpleFetch); + +} + +if(!dojo._hasResource["dojo.data.ItemFileWriteStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.data.ItemFileWriteStore"] = true; +dojo.provide("dojo.data.ItemFileWriteStore"); + + +dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, { + constructor: function(/* object */ keywordParameters){ + // keywordParameters: {typeMap: object) + // The structure of the typeMap object is as follows: + // { + // type0: function || object, + // type1: function || object, + // ... + // typeN: function || object + // } + // Where if it is a function, it is assumed to be an object constructor that takes the + // value of _value as the initialization parameters. It is serialized assuming object.toString() + // serialization. If it is an object, then it is assumed + // to be an object of general form: + // { + // type: function, //constructor. + // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. + // serialize: function(object) //The function that converts the object back into the proper file format form. + // } + + // ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs + this._features['dojo.data.api.Write'] = true; + this._features['dojo.data.api.Notification'] = true; + + // For keeping track of changes so that we can implement isDirty and revert + this._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; + + if(!this._datatypeMap['Date'].serialize){ + this._datatypeMap['Date'].serialize = function(obj){ + return dojo.date.stamp.toISOString(obj, {zulu:true}); + }; + } + //Disable only if explicitly set to false. + if(keywordParameters && (keywordParameters.referenceIntegrity === false)){ + this.referenceIntegrity = false; + } + + // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes + this._saveInProgress = false; + }, + + referenceIntegrity: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively. + + _assert: function(/* boolean */ condition){ + if(!condition){ + throw new Error("assertion failed in ItemFileWriteStore"); + } + }, + + _getIdentifierAttribute: function(){ + var identifierAttribute = this.getFeatures()['dojo.data.api.Identity']; + // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute))); + return identifierAttribute; + }, + + +/* dojo.data.api.Write */ + + newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){ + // summary: See dojo.data.api.Write.newItem() + + this._assert(!this._saveInProgress); + + if(!this._loadFinished){ + // We need to do this here so that we'll be able to find out what + // identifierAttribute was specified in the data file. + this._forceLoad(); + } + + if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){ + throw new Error("newItem() was passed something other than an object"); + } + var newIdentity = null; + var identifierAttribute = this._getIdentifierAttribute(); + if(identifierAttribute === Number){ + newIdentity = this._arrayOfAllItems.length; + }else{ + newIdentity = keywordArgs[identifierAttribute]; + if(typeof newIdentity === "undefined"){ + throw new Error("newItem() was not passed an identity for the new item"); + } + if(dojo.isArray(newIdentity)){ + throw new Error("newItem() was not passed an single-valued identity"); + } + } + + // make sure this identity is not already in use by another item, if identifiers were + // defined in the file. Otherwise it would be the item count, + // which should always be unique in this case. + if(this._itemsByIdentity){ + this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined"); + } + this._assert(typeof this._pending._newItems[newIdentity] === "undefined"); + this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined"); + + var newItem = {}; + newItem[this._storeRefPropName] = this; + newItem[this._itemNumPropName] = this._arrayOfAllItems.length; + if(this._itemsByIdentity){ + this._itemsByIdentity[newIdentity] = newItem; + //We have to set the identifier now, otherwise we can't look it + //up at calls to setValueorValues in parentInfo handling. + newItem[identifierAttribute] = [newIdentity]; + } + this._arrayOfAllItems.push(newItem); + + //We need to construct some data for the onNew call too... + var pInfo = null; + + // Now we need to check to see where we want to assign this thingm if any. + if(parentInfo && parentInfo.parent && parentInfo.attribute){ + pInfo = { + item: parentInfo.parent, + attribute: parentInfo.attribute, + oldValue: undefined + }; + + //See if it is multi-valued or not and handle appropriately + //Generally, all attributes are multi-valued for this store + //So, we only need to append if there are already values present. + var values = this.getValues(parentInfo.parent, parentInfo.attribute); + if(values && values.length > 0){ + var tempValues = values.slice(0, values.length); + if(values.length === 1){ + pInfo.oldValue = values[0]; + }else{ + pInfo.oldValue = values.slice(0, values.length); + } + tempValues.push(newItem); + this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false); + pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute); + }else{ + this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false); + pInfo.newValue = newItem; + } + }else{ + //Toplevel item, add to both top list as well as all list. + newItem[this._rootItemPropName]=true; + this._arrayOfTopLevelItems.push(newItem); + } + + this._pending._newItems[newIdentity] = newItem; + + //Clone over the properties to the new item + for(var key in keywordArgs){ + if(key === this._storeRefPropName || key === this._itemNumPropName){ + // Bummer, the user is trying to do something like + // newItem({_S:"foo"}). Unfortunately, our superclass, + // ItemFileReadStore, is already using _S in each of our items + // to hold private info. To avoid a naming collision, we + // need to move all our private info to some other property + // of all the items/objects. So, we need to iterate over all + // the items and do something like: + // item.__S = item._S; + // item._S = undefined; + // But first we have to make sure the new "__S" variable is + // not in use, which means we have to iterate over all the + // items checking for that. + throw new Error("encountered bug in ItemFileWriteStore.newItem"); + } + var value = keywordArgs[key]; + if(!dojo.isArray(value)){ + value = [value]; + } + newItem[key] = value; + if(this.referenceIntegrity){ + for(var i = 0; i < value.length; i++){ + var val = value[i]; + if(this.isItem(val)){ + this._addReferenceToMap(val, newItem, key); + } + } + } + } + this.onNew(newItem, pInfo); // dojo.data.api.Notification call + return newItem; // item + }, + + _removeArrayElement: function(/* Array */ array, /* anything */ element){ + var index = dojo.indexOf(array, element); + if(index != -1){ + array.splice(index, 1); + return true; + } + return false; + }, + + deleteItem: function(/* item */ item){ + // summary: See dojo.data.api.Write.deleteItem() + this._assert(!this._saveInProgress); + this._assertIsItem(item); + + // Remove this item from the _arrayOfAllItems, but leave a null value in place + // of the item, so as not to change the length of the array, so that in newItem() + // we can still safely do: newIdentity = this._arrayOfAllItems.length; + var indexInArrayOfAllItems = item[this._itemNumPropName]; + var identity = this.getIdentity(item); + + //If we have reference integrity on, we need to do reference cleanup for the deleted item + if(this.referenceIntegrity){ + //First scan all the attributes of this items for references and clean them up in the map + //As this item is going away, no need to track its references anymore. + + //Get the attributes list before we generate the backup so it + //doesn't pollute the attributes list. + var attributes = this.getAttributes(item); + + //Backup the map, we'll have to restore it potentially, in a revert. + if(item[this._reverseRefMap]){ + item["backup_" + this._reverseRefMap] = dojo.clone(item[this._reverseRefMap]); + } + + //TODO: This causes a reversion problem. This list won't be restored on revert since it is + //attached to the 'value'. item, not ours. Need to back tese up somehow too. + //Maybe build a map of the backup of the entries and attach it to the deleted item to be restored + //later. Or just record them and call _addReferenceToMap on them in revert. + dojo.forEach(attributes, function(attribute){ + dojo.forEach(this.getValues(item, attribute), function(value){ + if(this.isItem(value)){ + //We have to back up all the references we had to others so they can be restored on a revert. + if(!item["backupRefs_" + this._reverseRefMap]){ + item["backupRefs_" + this._reverseRefMap] = []; + } + item["backupRefs_" + this._reverseRefMap].push({id: this.getIdentity(value), attr: attribute}); + this._removeReferenceFromMap(value, item, attribute); + } + }, this); + }, this); + + //Next, see if we have references to this item, if we do, we have to clean them up too. + var references = item[this._reverseRefMap]; + if(references){ + //Look through all the items noted as references to clean them up. + for(var itemId in references){ + var containingItem = null; + if(this._itemsByIdentity){ + containingItem = this._itemsByIdentity[itemId]; + }else{ + containingItem = this._arrayOfAllItems[itemId]; + } + //We have a reference to a containing item, now we have to process the + //attributes and clear all references to the item being deleted. + if(containingItem){ + for(var attribute in references[itemId]){ + var oldValues = this.getValues(containingItem, attribute) || []; + var newValues = dojo.filter(oldValues, function(possibleItem){ + return !(this.isItem(possibleItem) && this.getIdentity(possibleItem) == identity); + }, this); + //Remove the note of the reference to the item and set the values on the modified attribute. + this._removeReferenceFromMap(item, containingItem, attribute); + if(newValues.length < oldValues.length){ + this._setValueOrValues(containingItem, attribute, newValues, true); + } + } + } + } + } + } + + this._arrayOfAllItems[indexInArrayOfAllItems] = null; + + item[this._storeRefPropName] = null; + if(this._itemsByIdentity){ + delete this._itemsByIdentity[identity]; + } + this._pending._deletedItems[identity] = item; + + //Remove from the toplevel items, if necessary... + if(item[this._rootItemPropName]){ + this._removeArrayElement(this._arrayOfTopLevelItems, item); + } + this.onDelete(item); // dojo.data.api.Notification call + return true; + }, + + setValue: function(/* item */ item, /* attribute-name-string */ attribute, /* almost anything */ value){ + // summary: See dojo.data.api.Write.set() + return this._setValueOrValues(item, attribute, value, true); // boolean + }, + + setValues: function(/* item */ item, /* attribute-name-string */ attribute, /* array */ values){ + // summary: See dojo.data.api.Write.setValues() + return this._setValueOrValues(item, attribute, values, true); // boolean + }, + + unsetAttribute: function(/* item */ item, /* attribute-name-string */ attribute){ + // summary: See dojo.data.api.Write.unsetAttribute() + return this._setValueOrValues(item, attribute, [], true); + }, + + _setValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){ + this._assert(!this._saveInProgress); + + // Check for valid arguments + this._assertIsItem(item); + this._assert(dojo.isString(attribute)); + this._assert(typeof newValueOrValues !== "undefined"); + + // Make sure the user isn't trying to change the item's identity + var identifierAttribute = this._getIdentifierAttribute(); + if(attribute == identifierAttribute){ + throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier."); + } + + // To implement the Notification API, we need to make a note of what + // the old attribute value was, so that we can pass that info when + // we call the onSet method. + var oldValueOrValues = this._getValueOrValues(item, attribute); + + var identity = this.getIdentity(item); + if(!this._pending._modifiedItems[identity]){ + // Before we actually change the item, we make a copy of it to + // record the original state, so that we'll be able to revert if + // the revert method gets called. If the item has already been + // modified then there's no need to do this now, since we already + // have a record of the original state. + var copyOfItemState = {}; + for(var key in item){ + if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){ + copyOfItemState[key] = item[key]; + }else if(key === this._reverseRefMap){ + copyOfItemState[key] = dojo.clone(item[key]); + }else{ + copyOfItemState[key] = item[key].slice(0, item[key].length); + } + } + // Now mark the item as dirty, and save the copy of the original state + this._pending._modifiedItems[identity] = copyOfItemState; + } + + // Okay, now we can actually change this attribute on the item + var success = false; + + if(dojo.isArray(newValueOrValues) && newValueOrValues.length === 0){ + + // If we were passed an empty array as the value, that counts + // as "unsetting" the attribute, so we need to remove this + // attribute from the item. + success = delete item[attribute]; + newValueOrValues = undefined; // used in the onSet Notification call below + + if(this.referenceIntegrity && oldValueOrValues){ + var oldValues = oldValueOrValues; + if(!dojo.isArray(oldValues)){ + oldValues = [oldValues]; + } + for(var i = 0; i < oldValues.length; i++){ + var value = oldValues[i]; + if(this.isItem(value)){ + this._removeReferenceFromMap(value, item, attribute); + } + } + } + }else{ + var newValueArray; + if(dojo.isArray(newValueOrValues)){ + var newValues = newValueOrValues; + // Unfortunately, it's not safe to just do this: + // newValueArray = newValues; + // Instead, we need to copy the array, which slice() does very nicely. + // This is so that our internal data structure won't + // get corrupted if the user mucks with the values array *after* + // calling setValues(). + newValueArray = newValueOrValues.slice(0, newValueOrValues.length); + }else{ + newValueArray = [newValueOrValues]; + } + + //We need to handle reference integrity if this is on. + //In the case of set, we need to see if references were added or removed + //and update the reference tracking map accordingly. + if(this.referenceIntegrity){ + if(oldValueOrValues){ + var oldValues = oldValueOrValues; + if(!dojo.isArray(oldValues)){ + oldValues = [oldValues]; + } + //Use an associative map to determine what was added/removed from the list. + //Should be O(n) performant. First look at all the old values and make a list of them + //Then for any item not in the old list, we add it. If it was already present, we remove it. + //Then we pass over the map and any references left it it need to be removed (IE, no match in + //the new values list). + var map = {}; + dojo.forEach(oldValues, function(possibleItem){ + if(this.isItem(possibleItem)){ + var id = this.getIdentity(possibleItem); + map[id.toString()] = true; + } + }, this); + dojo.forEach(newValueArray, function(possibleItem){ + if(this.isItem(possibleItem)){ + var id = this.getIdentity(possibleItem); + if(map[id.toString()]){ + delete map[id.toString()]; + }else{ + this._addReferenceToMap(possibleItem, item, attribute); + } + } + }, this); + for(var rId in map){ + var removedItem; + if(this._itemsByIdentity){ + removedItem = this._itemsByIdentity[rId]; + }else{ + removedItem = this._arrayOfAllItems[rId]; + } + this._removeReferenceFromMap(removedItem, item, attribute); + } + }else{ + //Everything is new (no old values) so we have to just + //insert all the references, if any. + for(var i = 0; i < newValueArray.length; i++){ + var value = newValueArray[i]; + if(this.isItem(value)){ + this._addReferenceToMap(value, item, attribute); + } + } + } + } + item[attribute] = newValueArray; + success = true; + } + + // Now we make the dojo.data.api.Notification call + if(callOnSet){ + this.onSet(item, attribute, oldValueOrValues, newValueOrValues); + } + return success; // boolean + }, + + _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){ + // summary: + // Method to add an reference map entry for an item and attribute. + // description: + // Method to add an reference map entry for an item and attribute. // + // refItem: + // The item that is referenced. + // parentItem: + // The item that holds the new reference to refItem. + // attribute: + // The attribute on parentItem that contains the new reference. + + var parentId = this.getIdentity(parentItem); + var references = refItem[this._reverseRefMap]; + + if(!references){ + references = refItem[this._reverseRefMap] = {}; + } + var itemRef = references[parentId]; + if(!itemRef){ + itemRef = references[parentId] = {}; + } + itemRef[attribute] = true; + }, + + _removeReferenceFromMap: function(/* item */ refItem, /* item */ parentItem, /*strin*/ attribute){ + // summary: + // Method to remove an reference map entry for an item and attribute. + // description: + // Method to remove an reference map entry for an item and attribute. This will + // also perform cleanup on the map such that if there are no more references at all to + // the item, its reference object and entry are removed. + // + // refItem: + // The item that is referenced. + // parentItem: + // The item holding a reference to refItem. + // attribute: + // The attribute on parentItem that contains the reference. + var identity = this.getIdentity(parentItem); + var references = refItem[this._reverseRefMap]; + var itemId; + if(references){ + for(itemId in references){ + if(itemId == identity){ + delete references[itemId][attribute]; + if(this._isEmpty(references[itemId])){ + delete references[itemId]; + } + } + } + if(this._isEmpty(references)){ + delete refItem[this._reverseRefMap]; + } + } + }, + + _dumpReferenceMap: function(){ + // summary: + // Function to dump the reverse reference map of all items in the store for debug purposes. + // description: + // Function to dump the reverse reference map of all items in the store for debug purposes. + var i; + for(i = 0; i < this._arrayOfAllItems.length; i++){ + var item = this._arrayOfAllItems[i]; + if(item && item[this._reverseRefMap]){ + console.log("Item: [" + this.getIdentity(item) + "] is referenced by: " + dojo.toJson(item[this._reverseRefMap])); + } + } + }, + + _getValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute){ + var valueOrValues = undefined; + if(this.hasAttribute(item, attribute)){ + var valueArray = this.getValues(item, attribute); + if(valueArray.length == 1){ + valueOrValues = valueArray[0]; + }else{ + valueOrValues = valueArray; + } + } + return valueOrValues; + }, + + _flatten: function(/* anything */ value){ + if(this.isItem(value)){ + var item = value; + // Given an item, return an serializable object that provides a + // reference to the item. + // For example, given kermit: + // var kermit = store.newItem({id:2, name:"Kermit"}); + // we want to return + // {_reference:2} + var identity = this.getIdentity(item); + var referenceObject = {_reference: identity}; + return referenceObject; + }else{ + if(typeof value === "object"){ + for(var type in this._datatypeMap){ + var typeMap = this._datatypeMap[type]; + if(dojo.isObject(typeMap) && !dojo.isFunction(typeMap)){ + if(value instanceof typeMap.type){ + if(!typeMap.serialize){ + throw new Error("ItemFileWriteStore: No serializer defined for type mapping: [" + type + "]"); + } + return {_type: type, _value: typeMap.serialize(value)}; + } + } else if(value instanceof typeMap){ + //SImple mapping, therefore, return as a toString serialization. + return {_type: type, _value: value.toString()}; + } + } + } + return value; + } + }, + + _getNewFileContentString: function(){ + // summary: + // Generate a string that can be saved to a file. + // The result should look similar to: + // http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json + var serializableStructure = {}; + + var identifierAttribute = this._getIdentifierAttribute(); + if(identifierAttribute !== Number){ + serializableStructure.identifier = identifierAttribute; + } + if(this._labelAttr){ + serializableStructure.label = this._labelAttr; + } + serializableStructure.items = []; + for(var i = 0; i < this._arrayOfAllItems.length; ++i){ + var item = this._arrayOfAllItems[i]; + if(item !== null){ + var serializableItem = {}; + for(var key in item){ + if(key !== this._storeRefPropName && key !== this._itemNumPropName && key !== this._reverseRefMap && key !== this._rootItemPropName){ + var attribute = key; + var valueArray = this.getValues(item, attribute); + if(valueArray.length == 1){ + serializableItem[attribute] = this._flatten(valueArray[0]); + }else{ + var serializableArray = []; + for(var j = 0; j < valueArray.length; ++j){ + serializableArray.push(this._flatten(valueArray[j])); + serializableItem[attribute] = serializableArray; + } + } + } + } + serializableStructure.items.push(serializableItem); + } + } + var prettyPrint = true; + return dojo.toJson(serializableStructure, prettyPrint); + }, + + _isEmpty: function(something){ + // summary: + // Function to determine if an array or object has no properties or values. + // something: + // The array or object to examine. + var empty = true; + if(dojo.isObject(something)){ + var i; + for(i in something){ + empty = false; + break; + } + }else if(dojo.isArray(something)){ + if(something.length > 0){ + empty = false; + } + } + return empty; //boolean + }, + + save: function(/* object */ keywordArgs){ + // summary: See dojo.data.api.Write.save() + this._assert(!this._saveInProgress); + + // this._saveInProgress is set to true, briefly, from when save is first called to when it completes + this._saveInProgress = true; + + var self = this; + var saveCompleteCallback = function(){ + self._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; + + self._saveInProgress = false; // must come after this._pending is cleared, but before any callbacks + if(keywordArgs && keywordArgs.onComplete){ + var scope = keywordArgs.scope || dojo.global; + keywordArgs.onComplete.call(scope); + } + }; + var saveFailedCallback = function(err){ + self._saveInProgress = false; + if(keywordArgs && keywordArgs.onError){ + var scope = keywordArgs.scope || dojo.global; + keywordArgs.onError.call(scope, err); + } + }; + + if(this._saveEverything){ + var newFileContentString = this._getNewFileContentString(); + this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString); + } + if(this._saveCustom){ + this._saveCustom(saveCompleteCallback, saveFailedCallback); + } + if(!this._saveEverything && !this._saveCustom){ + // Looks like there is no user-defined save-handler function. + // That's fine, it just means the datastore is acting as a "mock-write" + // store -- changes get saved in memory but don't get saved to disk. + saveCompleteCallback(); + } + }, + + revert: function(){ + // summary: See dojo.data.api.Write.revert() + this._assert(!this._saveInProgress); + + var identity; + for(identity in this._pending._modifiedItems){ + // find the original item and the modified item that replaced it + var copyOfItemState = this._pending._modifiedItems[identity]; + var modifiedItem = null; + if(this._itemsByIdentity){ + modifiedItem = this._itemsByIdentity[identity]; + }else{ + modifiedItem = this._arrayOfAllItems[identity]; + } + + // Restore the original item into a full-fledged item again, we want to try to + // keep the same object instance as if we don't it, causes bugs like #9022. + copyOfItemState[this._storeRefPropName] = this; + for(key in modifiedItem){ + delete modifiedItem[key]; + } + dojo.mixin(modifiedItem, copyOfItemState); + } + var deletedItem; + for(identity in this._pending._deletedItems){ + deletedItem = this._pending._deletedItems[identity]; + deletedItem[this._storeRefPropName] = this; + var index = deletedItem[this._itemNumPropName]; + + //Restore the reverse refererence map, if any. + if(deletedItem["backup_" + this._reverseRefMap]){ + deletedItem[this._reverseRefMap] = deletedItem["backup_" + this._reverseRefMap]; + delete deletedItem["backup_" + this._reverseRefMap]; + } + this._arrayOfAllItems[index] = deletedItem; + if(this._itemsByIdentity){ + this._itemsByIdentity[identity] = deletedItem; + } + if(deletedItem[this._rootItemPropName]){ + this._arrayOfTopLevelItems.push(deletedItem); + } + } + //We have to pass through it again and restore the reference maps after all the + //undeletes have occurred. + for(identity in this._pending._deletedItems){ + deletedItem = this._pending._deletedItems[identity]; + if(deletedItem["backupRefs_" + this._reverseRefMap]){ + dojo.forEach(deletedItem["backupRefs_" + this._reverseRefMap], function(reference){ + var refItem; + if(this._itemsByIdentity){ + refItem = this._itemsByIdentity[reference.id]; + }else{ + refItem = this._arrayOfAllItems[reference.id]; + } + this._addReferenceToMap(refItem, deletedItem, reference.attr); + }, this); + delete deletedItem["backupRefs_" + this._reverseRefMap]; + } + } + + for(identity in this._pending._newItems){ + var newItem = this._pending._newItems[identity]; + newItem[this._storeRefPropName] = null; + // null out the new item, but don't change the array index so + // so we can keep using _arrayOfAllItems.length. + this._arrayOfAllItems[newItem[this._itemNumPropName]] = null; + if(newItem[this._rootItemPropName]){ + this._removeArrayElement(this._arrayOfTopLevelItems, newItem); + } + if(this._itemsByIdentity){ + delete this._itemsByIdentity[identity]; + } + } + + this._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; + return true; // boolean + }, + + isDirty: function(/* item? */ item){ + // summary: See dojo.data.api.Write.isDirty() + if(item){ + // return true if the item is dirty + var identity = this.getIdentity(item); + return new Boolean(this._pending._newItems[identity] || + this._pending._modifiedItems[identity] || + this._pending._deletedItems[identity]).valueOf(); // boolean + }else{ + // return true if the store is dirty -- which means return true + // if there are any new items, dirty items, or modified items + if(!this._isEmpty(this._pending._newItems) || + !this._isEmpty(this._pending._modifiedItems) || + !this._isEmpty(this._pending._deletedItems)){ + return true; + } + return false; // boolean + } + }, + +/* dojo.data.api.Notification */ + + onSet: function(/* item */ item, + /*attribute-name-string*/ attribute, + /*object | array*/ oldValue, + /*object | array*/ newValue){ + // summary: See dojo.data.api.Notification.onSet() + + // No need to do anything. This method is here just so that the + // client code can connect observers to it. + }, + + onNew: function(/* item */ newItem, /*object?*/ parentInfo){ + // summary: See dojo.data.api.Notification.onNew() + + // No need to do anything. This method is here just so that the + // client code can connect observers to it. + }, + + onDelete: function(/* item */ deletedItem){ + // summary: See dojo.data.api.Notification.onDelete() + + // No need to do anything. This method is here just so that the + // client code can connect observers to it. + }, + + close: function(/* object? */ request){ + // summary: + // Over-ride of base close function of ItemFileReadStore to add in check for store state. + // description: + // Over-ride of base close function of ItemFileReadStore to add in check for store state. + // If the store is still dirty (unsaved changes), then an error will be thrown instead of + // clearing the internal state for reload from the url. + + //Clear if not dirty ... or throw an error + if(this.clearOnClose){ + if(!this.isDirty()){ + this.inherited(arguments); + }else{ + //Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved). + throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close."); + } + } + } +}); + +} + + +dojo.i18n._preloadLocalizations("dojo.nls.tt-rss-layer", ["ROOT","ar","ca","cs","da","de","de-de","el","en","en-gb","en-us","es","es-es","fi","fi-fi","fr","fr-fr","he","he-il","hu","it","it-it","ja","ja-jp","ko","ko-kr","nb","nl","nl-nl","pl","pt","pt-br","pt-pt","ru","sk","sl","sv","th","tr","xx","zh","zh-cn","zh-tw"]); diff --git a/lib/dojo/uacss.js b/lib/dojo/uacss.js index a7ba89e0..781ab9c3 100644 --- a/lib/dojo/uacss.js +++ b/lib/dojo/uacss.js @@ -5,24 +5,70 @@ */ -if(!dojo._hasResource["dojo.uacss"]){ -dojo._hasResource["dojo.uacss"]=true; +if(!dojo._hasResource["dojo.uacss"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.uacss"] = true; dojo.provide("dojo.uacss"); + (function(){ -var d=dojo,_1=d.doc.documentElement,ie=d.isIE,_2=d.isOpera,_3=Math.floor,ff=d.isFF,_4=d.boxModel.replace(/-/,""),_5={dj_ie:ie,dj_ie6:_3(ie)==6,dj_ie7:_3(ie)==7,dj_ie8:_3(ie)==8,dj_quirks:d.isQuirks,dj_iequirks:ie&&d.isQuirks,dj_opera:_2,dj_khtml:d.isKhtml,dj_webkit:d.isWebKit,dj_safari:d.isSafari,dj_chrome:d.isChrome,dj_gecko:d.isMozilla,dj_ff3:_3(ff)==3}; -_5["dj_"+_4]=true; -var _6=""; -for(var _7 in _5){ -if(_5[_7]){ -_6+=_7+" "; -} -} -_1.className=d.trim(_1.className+" "+_6); -dojo._loaders.unshift(function(){ -if(!dojo._isBodyLtr()){ -var _8="dj_rtl dijitRtl "+_6.replace(/ /g,"-rtl "); -_1.className=d.trim(_1.className+" "+_8); -} -}); + // summary: + // Applies pre-set CSS classes to the top-level HTML node, based on: + // - browser (ex: dj_ie) + // - browser version (ex: dj_ie6) + // - box model (ex: dj_contentBox) + // - text direction (ex: dijitRtl) + // + // In addition, browser, browser version, and box model are + // combined with an RTL flag when browser text is RTL. ex: dj_ie-rtl. + + var d = dojo, + html = d.doc.documentElement, + ie = d.isIE, + opera = d.isOpera, + maj = Math.floor, + ff = d.isFF, + boxModel = d.boxModel.replace(/-/,''), + + classes = { + dj_ie: ie, + dj_ie6: maj(ie) == 6, + dj_ie7: maj(ie) == 7, + dj_ie8: maj(ie) == 8, + dj_quirks: d.isQuirks, + dj_iequirks: ie && d.isQuirks, + + // NOTE: Opera not supported by dijit + dj_opera: opera, + + dj_khtml: d.isKhtml, + + dj_webkit: d.isWebKit, + dj_safari: d.isSafari, + dj_chrome: d.isChrome, + + dj_gecko: d.isMozilla, + dj_ff3: maj(ff) == 3 + }; // no dojo unsupported browsers + + classes["dj_" + boxModel] = true; + + // apply browser, browser version, and box model class names + var classStr = ""; + for(var clz in classes){ + if(classes[clz]){ + classStr += clz + " "; + } + } + html.className = d.trim(html.className + " " + classStr); + + // If RTL mode, then add dj_rtl flag plus repeat existing classes with -rtl extension. + // We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl). + // Unshift() is to run sniff code before the parser. + dojo._loaders.unshift(function(){ + if(!dojo._isBodyLtr()){ + var rtlClassStr = "dj_rtl dijitRtl " + classStr.replace(/ /g, "-rtl ") + html.className = d.trim(html.className + " " + rtlClassStr); + } + }); })(); + } diff --git a/lib/dojo/window.js b/lib/dojo/window.js index e6750dd7..c36eb4c2 100644 --- a/lib/dojo/window.js +++ b/lib/dojo/window.js @@ -5,109 +5,139 @@ */ -if(!dojo._hasResource["dojo.window"]){ -dojo._hasResource["dojo.window"]=true; +if(!dojo._hasResource["dojo.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.window"] = true; dojo.provide("dojo.window"); -dojo.window.getBox=function(){ -var _1=(dojo.doc.compatMode=="BackCompat")?dojo.body():dojo.doc.documentElement; -var _2=dojo._docScroll(); -return {w:_1.clientWidth,h:_1.clientHeight,l:_2.x,t:_2.y}; -}; -dojo.window.get=function(_3){ -if(dojo.isIE&&window!==document.parentWindow){ -_3.parentWindow.execScript("document._parentWindow = window;","Javascript"); -var _4=_3._parentWindow; -_3._parentWindow=null; -return _4; -} -return _3.parentWindow||_3.defaultView; + +dojo.window.getBox = function(){ + // summary: + // Returns the dimensions and scroll position of the viewable area of a browser window + + var scrollRoot = (dojo.doc.compatMode == 'BackCompat') ? dojo.body() : dojo.doc.documentElement; + + // get scroll position + var scroll = dojo._docScroll(); // scrollRoot.scrollTop/Left should work + return { w: scrollRoot.clientWidth, h: scrollRoot.clientHeight, l: scroll.x, t: scroll.y }; }; -dojo.window.scrollIntoView=function(_5,_6){ -try{ -_5=dojo.byId(_5); -var _7=_5.ownerDocument||dojo.doc,_8=_7.body||dojo.body(),_9=_7.documentElement||_8.parentNode,_a=dojo.isIE,_b=dojo.isWebKit; -if((!(dojo.isMoz||_a||_b||dojo.isOpera)||_5==_8||_5==_9)&&(typeof _5.scrollIntoView!="undefined")){ -_5.scrollIntoView(false); -return; -} -var _c=_7.compatMode=="BackCompat",_d=_c?_8:_9,_e=_b?_8:_d,_f=_d.clientWidth,_10=_d.clientHeight,rtl=!dojo._isBodyLtr(),_11=_6||dojo.position(_5),el=_5.parentNode,_12=function(el){ -return ((_a<=6||(_a&&_c))?false:(dojo.style(el,"position").toLowerCase()=="fixed")); + +dojo.window.get = function(doc){ + // summary: + // Get window object associated with document doc + + // In some IE versions (at least 6.0), document.parentWindow does not return a + // reference to the real window object (maybe a copy), so we must fix it as well + // We use IE specific execScript to attach the real window reference to + // document._parentWindow for later use + if(dojo.isIE && window !== document.parentWindow){ + /* + In IE 6, only the variable "window" can be used to connect events (others + may be only copies). + */ + doc.parentWindow.execScript("document._parentWindow = window;", "Javascript"); + //to prevent memory leak, unset it after use + //another possibility is to add an onUnload handler which seems overkill to me (liucougar) + var win = doc._parentWindow; + doc._parentWindow = null; + return win; // Window + } + + return doc.parentWindow || doc.defaultView; // Window }; -if(_12(_5)){ -return; -} -while(el){ -if(el==_8){ -el=_e; -} -var _13=dojo.position(el),_14=_12(el); -if(el==_e){ -_13.w=_f; -_13.h=_10; -if(_e==_9&&_a&&rtl){ -_13.x+=_e.offsetWidth-_13.w; -} -if(_13.x<0||!_a){ -_13.x=0; -} -if(_13.y<0||!_a){ -_13.y=0; -} -}else{ -var pb=dojo._getPadBorderExtents(el); -_13.w-=pb.w; -_13.h-=pb.h; -_13.x+=pb.l; -_13.y+=pb.t; -} -if(el!=_e){ -var _15=el.clientWidth,_16=_13.w-_15; -if(_15>0&&_16>0){ -_13.w=_15; -if(_a&&rtl){ -_13.x+=_16; -} -} -_15=el.clientHeight; -_16=_13.h-_15; -if(_15>0&&_16>0){ -_13.h=_15; -} -} -if(_14){ -if(_13.y<0){ -_13.h+=_13.y; -_13.y=0; -} -if(_13.x<0){ -_13.w+=_13.x; -_13.x=0; -} -if(_13.y+_13.h>_10){ -_13.h=_10-_13.y; -} -if(_13.x+_13.w>_f){ -_13.w=_f-_13.x; -} -} -var l=_11.x-_13.x,t=_11.y-Math.max(_13.y,0),r=l+_11.w-_13.w,bot=t+_11.h-_13.h; -if(r*l>0){ -var s=Math[l<0?"max":"min"](l,r); -_11.x+=el.scrollLeft; -el.scrollLeft+=(_a>=8&&!_c&&rtl)?-s:s; -_11.x-=el.scrollLeft; -} -if(bot*t>0){ -_11.y+=el.scrollTop; -el.scrollTop+=Math[t<0?"max":"min"](t,bot); -_11.y-=el.scrollTop; -} -el=(el!=_e)&&!_14&&el.parentNode; -} -} -catch(error){ -console.error("scrollIntoView: "+error); -_5.scrollIntoView(false); -} + +dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){ + // summary: + // Scroll the passed node into view, if it is not already. + + // don't rely on node.scrollIntoView working just because the function is there + + try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method + node = dojo.byId(node); + var doc = node.ownerDocument || dojo.doc, + body = doc.body || dojo.body(), + html = doc.documentElement || body.parentNode, + isIE = dojo.isIE, isWK = dojo.isWebKit; + // if an untested browser, then use the native method + if((!(dojo.isMoz || isIE || isWK || dojo.isOpera) || node == body || node == html) && (typeof node.scrollIntoView != "undefined")){ + node.scrollIntoView(false); // short-circuit to native if possible + return; + } + var backCompat = doc.compatMode == 'BackCompat', + clientAreaRoot = backCompat? body : html, + scrollRoot = isWK ? body : clientAreaRoot, + rootWidth = clientAreaRoot.clientWidth, + rootHeight = clientAreaRoot.clientHeight, + rtl = !dojo._isBodyLtr(), + nodePos = pos || dojo.position(node), + el = node.parentNode, + isFixed = function(el){ + return ((isIE <= 6 || (isIE && backCompat))? false : (dojo.style(el, 'position').toLowerCase() == "fixed")); + }; + if(isFixed(node)){ return; } // nothing to do + + while(el){ + if(el == body){ el = scrollRoot; } + var elPos = dojo.position(el), + fixedPos = isFixed(el); + + if(el == scrollRoot){ + elPos.w = rootWidth; elPos.h = rootHeight; + if(scrollRoot == html && isIE && rtl){ elPos.x += scrollRoot.offsetWidth-elPos.w; } // IE workaround where scrollbar causes negative x + if(elPos.x < 0 || !isIE){ elPos.x = 0; } // IE can have values > 0 + if(elPos.y < 0 || !isIE){ elPos.y = 0; } + }else{ + var pb = dojo._getPadBorderExtents(el); + elPos.w -= pb.w; elPos.h -= pb.h; elPos.x += pb.l; elPos.y += pb.t; + } + + if(el != scrollRoot){ // body, html sizes already have the scrollbar removed + var clientSize = el.clientWidth, + scrollBarSize = elPos.w - clientSize; + if(clientSize > 0 && scrollBarSize > 0){ + elPos.w = clientSize; + if(isIE && rtl){ elPos.x += scrollBarSize; } + } + clientSize = el.clientHeight; + scrollBarSize = elPos.h - clientSize; + if(clientSize > 0 && scrollBarSize > 0){ + elPos.h = clientSize; + } + } + if(fixedPos){ // bounded by viewport, not parents + if(elPos.y < 0){ + elPos.h += elPos.y; elPos.y = 0; + } + if(elPos.x < 0){ + elPos.w += elPos.x; elPos.x = 0; + } + if(elPos.y + elPos.h > rootHeight){ + elPos.h = rootHeight - elPos.y; + } + if(elPos.x + elPos.w > rootWidth){ + elPos.w = rootWidth - elPos.x; + } + } + // calculate overflow in all 4 directions + var l = nodePos.x - elPos.x, // beyond left: < 0 + t = nodePos.y - Math.max(elPos.y, 0), // beyond top: < 0 + r = l + nodePos.w - elPos.w, // beyond right: > 0 + bot = t + nodePos.h - elPos.h; // beyond bottom: > 0 + if(r * l > 0){ + var s = Math[l < 0? "max" : "min"](l, r); + nodePos.x += el.scrollLeft; + el.scrollLeft += (isIE >= 8 && !backCompat && rtl)? -s : s; + nodePos.x -= el.scrollLeft; + } + if(bot * t > 0){ + nodePos.y += el.scrollTop; + el.scrollTop += Math[t < 0? "max" : "min"](t, bot); + nodePos.y -= el.scrollTop; + } + el = (el != scrollRoot) && !fixedPos && el.parentNode; + } + }catch(error){ + console.error('scrollIntoView: ' + error); + node.scrollIntoView(false); + } }; + } diff --git a/prefs.js b/prefs.js index 93f5b06c..031e7217 100644 --- a/prefs.js +++ b/prefs.js @@ -886,7 +886,7 @@ function init() { try { - dojo.require("dijit.layout.TabContainer"); + /* dojo.require("dijit.layout.TabContainer"); dojo.require("dijit.layout.BorderContainer"); dojo.require("dijit.layout.AccordionContainer"); dojo.require("dijit.layout.ContentPane"); @@ -909,7 +909,7 @@ function init() { dojo.require("dijit.InlineEditBox"); dojo.require("dijit.ColorPalette"); dojo.require("dijit.ProgressBar"); - dojo.require("dijit.form.SimpleTextarea"); + dojo.require("dijit.form.SimpleTextarea"); */ dojo.registerModulePath("lib", ".."); dojo.registerModulePath("fox", "../.."); @@ -919,6 +919,8 @@ function init() { dojo.require("fox.PrefFilterTree"); dojo.require("fox.PrefLabelTree"); + dojo.parser.parse(); + dojo.addOnLoad(function() { loading_set_progress(50); diff --git a/prefs.php b/prefs.php index ebde59dd..41744357 100644 --- a/prefs.php +++ b/prefs.php @@ -35,7 +35,9 @@ <script type="text/javascript" src="lib/prototype.js"></script> <script type="text/javascript" src="lib/position.js"></script> <script type="text/javascript" src="lib/scriptaculous/scriptaculous.js?load=effects,dragdrop,controls"></script> - <script type="text/javascript" src="lib/dojo/dojo.js" djConfig="parseOnLoad: true"></script> + <script type="text/javascript" src="lib/dojo/dojo.js"></script> + <script type="text/javascript" src="lib/dijit/dijit.js"></script> + <script type="text/javascript" src="lib/dojo/tt-rss-layer.js"></script> <script type="text/javascript" charset="utf-8" src="localized_js.php?<?php echo $dt_add ?>"></script> diff --git a/tt-rss.js b/tt-rss.js index 5b4947b4..690e35bc 100644 --- a/tt-rss.js +++ b/tt-rss.js @@ -276,7 +276,9 @@ function init() { try { //Form.disable("main_toolbar_form"); - dojo.require("dijit.layout.BorderContainer"); + // Our layer takes care of Dojo dependencies. + + /* dojo.require("dijit.layout.BorderContainer"); dojo.require("dijit.layout.TabContainer"); dojo.require("dijit.layout.ContentPane"); dojo.require("dijit.Dialog"); @@ -293,12 +295,14 @@ function init() { dojo.require("dijit.Toolbar"); dojo.require("dijit.ProgressBar"); dojo.require("dijit.Menu"); - dojo.require("dojo.parser"); + dojo.require("dojo.parser"); */ dojo.registerModulePath("fox", "../.."); dojo.require("fox.FeedTree"); + dojo.parser.parse(); + if (typeof themeBeforeLayout == 'function') { themeBeforeLayout(); } diff --git a/tt-rss.php b/tt-rss.php index 49c882db..63c922f0 100644 --- a/tt-rss.php +++ b/tt-rss.php @@ -35,7 +35,10 @@ <script type="text/javascript" src="lib/prototype.js"></script> <script type="text/javascript" src="lib/scriptaculous/scriptaculous.js?load=effects,dragdrop,controls"></script> - <script type="text/javascript" src="lib/dojo/dojo.js" djConfig="parseOnLoad: true"></script> + <script type="text/javascript" src="lib/dojo/dojo.js"></script> + <script type="text/javascript" src="lib/dijit/dijit.js"></script> + <script type="text/javascript" src="lib/dojo/tt-rss-layer.js"></script> + <script type="text/javascript" charset="utf-8" src="localized_js.php?<?php echo $dt_add ?>"></script> <script type="text/javascript" charset="utf-8" src="tt-rss.js?<?php echo $dt_add ?>"></script> <script type="text/javascript" charset="utf-8" src="functions.js?<?php echo $dt_add ?>"></script>