| /* |
| Copyright (c) 2004-2006, The Dojo Foundation |
| All Rights Reserved. |
| |
| Licensed under the Academic Free License version 2.1 or above OR the |
| modified BSD license. For more information on Dojo licensing, see: |
| |
| http://dojotoolkit.org/community/licensing.shtml |
| */ |
| |
| dojo.provide("layer.widget"); |
| dojo.provide("dojo.namespaces.dojo"); |
| dojo.require("dojo.ns"); |
| |
| (function(){ |
| // Mapping of all widget short names to their full package names |
| // This is used for widget autoloading - no dojo.require() is necessary. |
| // If you use a widget in markup or create one dynamically, then this |
| // mapping is used to find and load any dependencies not already loaded. |
| // You should use your own namespace for any custom widgets. |
| // For extra widgets you use, dojo.declare() may be used to explicitly load them. |
| // Experimental and deprecated widgets are not included in this table |
| var map = { |
| html: { |
| "accordioncontainer": "dojo.widget.AccordionContainer", |
| "animatedpng": "dojo.widget.AnimatedPng", |
| "button": "dojo.widget.Button", |
| "chart": "dojo.widget.Chart", |
| "checkbox": "dojo.widget.Checkbox", |
| "clock": "dojo.widget.Clock", |
| "colorpalette": "dojo.widget.ColorPalette", |
| "combobox": "dojo.widget.ComboBox", |
| "combobutton": "dojo.widget.Button", |
| "contentpane": "dojo.widget.ContentPane", |
| "currencytextbox": "dojo.widget.CurrencyTextbox", |
| "datepicker": "dojo.widget.DatePicker", |
| "datetextbox": "dojo.widget.DateTextbox", |
| "debugconsole": "dojo.widget.DebugConsole", |
| "dialog": "dojo.widget.Dialog", |
| "dropdownbutton": "dojo.widget.Button", |
| "dropdowndatepicker": "dojo.widget.DropdownDatePicker", |
| "dropdowntimepicker": "dojo.widget.DropdownTimePicker", |
| "emaillisttextbox": "dojo.widget.InternetTextbox", |
| "emailtextbox": "dojo.widget.InternetTextbox", |
| "editor": "dojo.widget.Editor", |
| "editor2": "dojo.widget.Editor2", |
| "filteringtable": "dojo.widget.FilteringTable", |
| "fisheyelist": "dojo.widget.FisheyeList", |
| "fisheyelistitem": "dojo.widget.FisheyeList", |
| "floatingpane": "dojo.widget.FloatingPane", |
| "modalfloatingpane": "dojo.widget.FloatingPane", |
| "form": "dojo.widget.Form", |
| "googlemap": "dojo.widget.GoogleMap", |
| "inlineeditbox": "dojo.widget.InlineEditBox", |
| "integerspinner": "dojo.widget.Spinner", |
| "integertextbox": "dojo.widget.IntegerTextbox", |
| "ipaddresstextbox": "dojo.widget.InternetTextbox", |
| "layoutcontainer": "dojo.widget.LayoutContainer", |
| "linkpane": "dojo.widget.LinkPane", |
| "popupmenu2": "dojo.widget.Menu2", |
| "menuitem2": "dojo.widget.Menu2", |
| "menuseparator2": "dojo.widget.Menu2", |
| "menubar2": "dojo.widget.Menu2", |
| "menubaritem2": "dojo.widget.Menu2", |
| "pagecontainer": "dojo.widget.PageContainer", |
| "pagecontroller": "dojo.widget.PageContainer", |
| "popupcontainer": "dojo.widget.PopupContainer", |
| "progressbar": "dojo.widget.ProgressBar", |
| "radiogroup": "dojo.widget.RadioGroup", |
| "realnumbertextbox": "dojo.widget.RealNumberTextbox", |
| "regexptextbox": "dojo.widget.RegexpTextbox", |
| "repeater": "dojo.widget.Repeater", |
| "resizabletextarea": "dojo.widget.ResizableTextarea", |
| "richtext": "dojo.widget.RichText", |
| "select": "dojo.widget.Select", |
| "show": "dojo.widget.Show", |
| "showaction": "dojo.widget.ShowAction", |
| "showslide": "dojo.widget.ShowSlide", |
| "slidervertical": "dojo.widget.Slider", |
| "sliderhorizontal": "dojo.widget.Slider", |
| "slider":"dojo.widget.Slider", |
| "slideshow": "dojo.widget.SlideShow", |
| "sortabletable": "dojo.widget.SortableTable", |
| "splitcontainer": "dojo.widget.SplitContainer", |
| "tabcontainer": "dojo.widget.TabContainer", |
| "tabcontroller": "dojo.widget.TabContainer", |
| "taskbar": "dojo.widget.TaskBar", |
| "textbox": "dojo.widget.Textbox", |
| "timepicker": "dojo.widget.TimePicker", |
| "timetextbox": "dojo.widget.DateTextbox", |
| "titlepane": "dojo.widget.TitlePane", |
| "toaster": "dojo.widget.Toaster", |
| "toggler": "dojo.widget.Toggler", |
| "toolbar": "dojo.widget.Toolbar", |
| "toolbarcontainer": "dojo.widget.Toolbar", |
| "toolbaritem": "dojo.widget.Toolbar", |
| "toolbarbuttongroup": "dojo.widget.Toolbar", |
| "toolbarbutton": "dojo.widget.Toolbar", |
| "toolbardialog": "dojo.widget.Toolbar", |
| "toolbarmenu": "dojo.widget.Toolbar", |
| "toolbarseparator": "dojo.widget.Toolbar", |
| "toolbarspace": "dojo.widget.Toolbar", |
| "toolbarselect": "dojo.widget.Toolbar", |
| "toolbarcolordialog": "dojo.widget.Toolbar", |
| "tooltip": "dojo.widget.Tooltip", |
| "tree": "dojo.widget.Tree", |
| "treebasiccontroller": "dojo.widget.TreeBasicController", |
| "treecontextmenu": "dojo.widget.TreeContextMenu", |
| "treedisablewrapextension": "dojo.widget.TreeDisableWrapExtension", |
| "treedociconextension": "dojo.widget.TreeDocIconExtension", |
| "treeeditor": "dojo.widget.TreeEditor", |
| "treeemphasizeonselect": "dojo.widget.TreeEmphasizeOnSelect", |
| "treeexpandtonodeonselect": "dojo.widget.TreeExpandToNodeOnSelect", |
| "treelinkextension": "dojo.widget.TreeLinkExtension", |
| "treeloadingcontroller": "dojo.widget.TreeLoadingController", |
| "treemenuitem": "dojo.widget.TreeContextMenu", |
| "treenode": "dojo.widget.TreeNode", |
| "treerpccontroller": "dojo.widget.TreeRPCController", |
| "treeselector": "dojo.widget.TreeSelector", |
| "treetoggleonselect": "dojo.widget.TreeToggleOnSelect", |
| "treev3": "dojo.widget.TreeV3", |
| "treebasiccontrollerv3": "dojo.widget.TreeBasicControllerV3", |
| "treecontextmenuv3": "dojo.widget.TreeContextMenuV3", |
| "treedndcontrollerv3": "dojo.widget.TreeDndControllerV3", |
| "treeloadingcontrollerv3": "dojo.widget.TreeLoadingControllerV3", |
| "treemenuitemv3": "dojo.widget.TreeContextMenuV3", |
| "treerpccontrollerv3": "dojo.widget.TreeRpcControllerV3", |
| "treeselectorv3": "dojo.widget.TreeSelectorV3", |
| "urltextbox": "dojo.widget.InternetTextbox", |
| "usphonenumbertextbox": "dojo.widget.UsTextbox", |
| "ussocialsecuritynumbertextbox": "dojo.widget.UsTextbox", |
| "usstatetextbox": "dojo.widget.UsTextbox", |
| "usziptextbox": "dojo.widget.UsTextbox", |
| "validationtextbox": "dojo.widget.ValidationTextbox", |
| "treeloadingcontroller": "dojo.widget.TreeLoadingController", |
| "wizardcontainer": "dojo.widget.Wizard", |
| "wizardpane": "dojo.widget.Wizard", |
| "yahoomap": "dojo.widget.YahooMap" |
| }, |
| svg: { |
| "chart": "dojo.widget.svg.Chart" |
| }, |
| vml: { |
| "chart": "dojo.widget.vml.Chart" |
| } |
| }; |
| |
| dojo.addDojoNamespaceMapping = function(/*String*/shortName, /*String*/packageName){ |
| // summary: |
| // Add an entry to the mapping table for the dojo: namespace |
| // |
| // shortName: the name to be used as the widget's tag name in the dojo: namespace |
| // packageName: the path to the Javascript module in dotted package notation |
| map[shortName]=packageName; |
| }; |
| |
| function dojoNamespaceResolver(name, domain){ |
| if(!domain){ domain="html"; } |
| if(!map[domain]){ return null; } |
| return map[domain][name]; |
| } |
| |
| dojo.registerNamespaceResolver("dojo", dojoNamespaceResolver); |
| })(); |
| |
| dojo.provide("dojo.xml.Parse"); |
| dojo.require("dojo.dom"); |
| |
| //TODO: determine dependencies |
| // currently has dependency on dojo.xml.DomUtil nodeTypes constants... |
| |
| // using documentFragment nomenclature to generalize in case we don't want to require passing a collection of nodes with a single parent |
| |
| dojo.xml.Parse = function(){ |
| // summary: |
| // generic class for taking a DOM node and parsing it into an object |
| // based on the "dojo tag name" of that node. |
| // |
| // supported dojoTagName's: |
| // <prefix:tag> => prefix:tag |
| // <dojo:tag> => dojo:tag |
| // <dojoTag> => dojo:tag |
| // <tag dojoType="type"> => dojo:type |
| // <tag dojoType="prefix:type"> => prefix:type |
| // <tag dojo:type="type"> => dojo:type |
| // <tag class="classa dojo-type classb"> => dojo:type |
| |
| var isIE = ((dojo.render.html.capable)&&(dojo.render.html.ie)); |
| |
| // get normalized (lowercase) tagName |
| // some browsers report tagNames in lowercase no matter what |
| function getTagName(node){ |
| /* |
| return ((node)&&(node["tagName"]) ? node.tagName.toLowerCase() : ''); |
| */ |
| try{ |
| return node.tagName.toLowerCase(); |
| }catch(e){ |
| return ""; |
| } |
| } |
| |
| // locate dojo qualified tag name |
| function getDojoTagName(node){ |
| var tagName = getTagName(node); |
| if (!tagName){ |
| return ''; |
| } |
| // any registered tag |
| if((dojo.widget)&&(dojo.widget.tags[tagName])){ |
| return tagName; |
| } |
| // <prefix:tag> => prefix:tag |
| var p = tagName.indexOf(":"); |
| if(p>=0){ |
| return tagName; |
| } |
| // <dojo:tag> => dojo:tag |
| if(tagName.substr(0,5) == "dojo:"){ |
| return tagName; |
| } |
| if(dojo.render.html.capable && dojo.render.html.ie && node.scopeName != 'HTML'){ |
| return node.scopeName.toLowerCase() + ':' + tagName; |
| } |
| // <dojoTag> => dojo:tag |
| if(tagName.substr(0,4) == "dojo"){ |
| // FIXME: this assumes tag names are always lower case |
| return "dojo:" + tagName.substring(4); |
| } |
| // <tag dojoType="prefix:type"> => prefix:type |
| // <tag dojoType="type"> => dojo:type |
| var djt = node.getAttribute("dojoType") || node.getAttribute("dojotype"); |
| if(djt){ |
| if (djt.indexOf(":")<0){ |
| djt = "dojo:"+djt; |
| } |
| return djt.toLowerCase(); |
| } |
| // <tag dojo:type="type"> => dojo:type |
| djt = node.getAttributeNS && node.getAttributeNS(dojo.dom.dojoml,"type"); |
| if(djt){ |
| return "dojo:" + djt.toLowerCase(); |
| } |
| // <tag dojo:type="type"> => dojo:type |
| try{ |
| // FIXME: IE really really doesn't like this, so we squelch errors for it |
| djt = node.getAttribute("dojo:type"); |
| }catch(e){ |
| // FIXME: log? |
| } |
| if(djt){ return "dojo:"+djt.toLowerCase(); } |
| // <tag class="classa dojo-type classb"> => dojo:type |
| if((dj_global["djConfig"])&&(!djConfig["ignoreClassNames"])){ |
| // FIXME: should we make this optionally enabled via djConfig? |
| var classes = node.className||node.getAttribute("class"); |
| // FIXME: following line, without check for existence of classes.indexOf |
| // breaks firefox 1.5's svg widgets |
| if((classes )&&(classes.indexOf)&&(classes.indexOf("dojo-")!=-1)){ |
| var aclasses = classes.split(" "); |
| for(var x=0, c=aclasses.length; x<c; x++){ |
| if(aclasses[x].slice(0, 5) == "dojo-"){ |
| return "dojo:"+aclasses[x].substr(5).toLowerCase(); |
| } |
| } |
| } |
| } |
| // no dojo-qualified name |
| return ''; |
| } |
| |
| |
| this.parseElement = function( /*DomNode*/node, |
| /*Boolean*/hasParentNodeSet, |
| /*Boolean*/optimizeForDojoML, |
| /*Integer*/thisIdx ){ |
| // summary: |
| // recursively parse the passed node, returning a normalized data |
| // structure that represents the "attributes of interest" of said |
| // elements. If optimizeForDojoML is true, only nodes that contain |
| // a "dojo tag name" will be inspected for attributes. |
| // node: the DomNode to be treated as the root of inspection |
| // hasParentNodeSet: no-op, please pass "null" |
| // optimizeForDojoML: should we ignore non-Dojo nodes? Defaults to false. |
| // thisIdx: |
| // a way to specify a synthetic "index" property in the resulting |
| // data structure. Otherwise the index property of the top-level |
| // return element is always "0". |
| |
| // TODOC: document return structure of a non-trivial element set |
| |
| // run shortcuts to bail out of processing up front to save time and |
| // object alloc if possible. |
| var tagName = getTagName(node); |
| //There's a weird bug in IE where it counts end tags, e.g. </dojo:button> as nodes that should be parsed. Ignore these |
| if(isIE && tagName.indexOf("/")==0){ return null; } |
| |
| try{ |
| var attr = node.getAttribute("parseWidgets"); |
| if(attr && attr.toLowerCase() == "false"){ |
| return {}; |
| } |
| }catch(e){/*continue*/} |
| |
| |
| // look for a dojoml qualified name |
| // process dojoml only when optimizeForDojoML is true |
| var process = true; |
| if(optimizeForDojoML){ |
| var dojoTagName = getDojoTagName(node); |
| tagName = dojoTagName || tagName; |
| process = Boolean(dojoTagName); |
| } |
| |
| var parsedNodeSet = {}; |
| parsedNodeSet[tagName] = []; |
| var pos = tagName.indexOf(":"); |
| if(pos>0){ |
| var ns = tagName.substring(0,pos); |
| parsedNodeSet["ns"] = ns; |
| // honor user namespace filters |
| if((dojo.ns)&&(!dojo.ns.allow(ns))){process=false;} |
| } |
| |
| if(process){ |
| var attributeSet = this.parseAttributes(node); |
| for(var attr in attributeSet){ |
| if((!parsedNodeSet[tagName][attr])||(typeof parsedNodeSet[tagName][attr] != "array")){ |
| parsedNodeSet[tagName][attr] = []; |
| } |
| parsedNodeSet[tagName][attr].push(attributeSet[attr]); |
| } |
| // FIXME: we might want to make this optional or provide cloning instead of |
| // referencing, but for now, we include a node reference to allow |
| // instantiated components to figure out their "roots" |
| parsedNodeSet[tagName].nodeRef = node; |
| parsedNodeSet.tagName = tagName; |
| parsedNodeSet.index = thisIdx||0; |
| } |
| |
| var count = 0; |
| for(var i = 0; i < node.childNodes.length; i++){ |
| var tcn = node.childNodes.item(i); |
| switch(tcn.nodeType){ |
| case dojo.dom.ELEMENT_NODE: // element nodes, call this function recursively |
| var ctn = getDojoTagName(tcn) || getTagName(tcn); |
| if(!parsedNodeSet[ctn]){ |
| parsedNodeSet[ctn] = []; |
| } |
| parsedNodeSet[ctn].push(this.parseElement(tcn, true, optimizeForDojoML, count)); |
| if( (tcn.childNodes.length == 1)&& |
| (tcn.childNodes.item(0).nodeType == dojo.dom.TEXT_NODE)){ |
| parsedNodeSet[ctn][parsedNodeSet[ctn].length-1].value = tcn.childNodes.item(0).nodeValue; |
| } |
| count++; |
| break; |
| case dojo.dom.TEXT_NODE: // if a single text node is the child, treat it as an attribute |
| if(node.childNodes.length == 1){ |
| parsedNodeSet[tagName].push({ value: node.childNodes.item(0).nodeValue }); |
| } |
| break; |
| default: break; |
| /* |
| case dojo.dom.ATTRIBUTE_NODE: // attribute node... not meaningful here |
| break; |
| case dojo.dom.CDATA_SECTION_NODE: // cdata section... not sure if this would ever be meaningful... might be... |
| break; |
| case dojo.dom.ENTITY_REFERENCE_NODE: // entity reference node... not meaningful here |
| break; |
| case dojo.dom.ENTITY_NODE: // entity node... not sure if this would ever be meaningful |
| break; |
| case dojo.dom.PROCESSING_INSTRUCTION_NODE: // processing instruction node... not meaningful here |
| break; |
| case dojo.dom.COMMENT_NODE: // comment node... not not sure if this would ever be meaningful |
| break; |
| case dojo.dom.DOCUMENT_NODE: // document node... not sure if this would ever be meaningful |
| break; |
| case dojo.dom.DOCUMENT_TYPE_NODE: // document type node... not meaningful here |
| break; |
| case dojo.dom.DOCUMENT_FRAGMENT_NODE: // document fragment node... not meaningful here |
| break; |
| case dojo.dom.NOTATION_NODE:// notation node... not meaningful here |
| break; |
| */ |
| } |
| } |
| //return (hasParentNodeSet) ? parsedNodeSet[node.tagName] : parsedNodeSet; |
| //if(parsedNodeSet.tagName)dojo.debug("parseElement: RETURNING NODE WITH TAGNAME "+parsedNodeSet.tagName); |
| return parsedNodeSet; |
| }; |
| |
| |
| /* parses a set of attributes on a node into an object tree */ |
| this.parseAttributes = function(/*DomNode*/node){ |
| // summary: |
| // creates an attribute object that maps attribute values for the |
| // passed node. Note that this is similar to creating a JSON |
| // representation of a DOM node. |
| // usage: |
| // a node with the following serialization: |
| // <div foo="bar" baz="thud">...</div> |
| // would yeild the following return structure when passed into this |
| // function: |
| // { |
| // "foo": { |
| // "value": "bar" |
| // }, |
| // "baz": { |
| // "value": "thud" |
| // } |
| // } |
| // |
| var parsedAttributeSet = {}; |
| var atts = node.attributes; |
| // TODO: should we allow for duplicate attributes at this point... |
| // would any of the relevant dom implementations even allow this? |
| var attnode, i=0; |
| while((attnode=atts[i++])){ |
| if(isIE){ |
| if(!attnode){ continue; } |
| if((typeof attnode == "object")&& |
| (typeof attnode.nodeValue == 'undefined')|| |
| (attnode.nodeValue == null)|| |
| (attnode.nodeValue == '')){ |
| continue; |
| } |
| } |
| |
| var nn = attnode.nodeName.split(":"); |
| nn = (nn.length == 2) ? nn[1] : attnode.nodeName; |
| |
| parsedAttributeSet[nn] = { |
| value: attnode.nodeValue |
| }; |
| } |
| return parsedAttributeSet; |
| }; |
| }; |
| |
| dojo.provide("dojo.widget.Widget"); |
| |
| dojo.require("dojo.lang.func"); |
| dojo.require("dojo.lang.array"); |
| dojo.require("dojo.lang.extras"); |
| dojo.require("dojo.lang.declare"); |
| dojo.require("dojo.ns"); |
| dojo.require("dojo.widget.Manager"); |
| dojo.require("dojo.event.*"); |
| |
| dojo.declare("dojo.widget.Widget", null, |
| function(){ |
| // these properties aren't primitives and need to be created on a per-item |
| // basis. |
| |
| // children: Array |
| // a list of all of the widgets that have been added as children of |
| // this component. Should only have values if isContainer is true. |
| this.children = []; |
| |
| // extraArgs: Object |
| // a map of properties which the widget system tried to assign from |
| // user input but did not correspond to any of the properties set on |
| // the class prototype. These names will also be available in all |
| // lower-case form in this map |
| this.extraArgs = {}; |
| }, |
| { |
| // parent: Widget |
| // the parent of this widget |
| parent: null, |
| |
| // isTopLevel: Boolean |
| // should this widget eat all events that bubble up to it? |
| // obviously, top-level and modal widgets should set these appropriately |
| isTopLevel: false, |
| |
| // disabled: Boolean |
| // should this widget respond to user input? |
| // in markup, this is specified as "disabled='disabled'", or just "disabled" |
| disabled: false, |
| |
| // isContainer: Boolean |
| // can this widget contain other widgets? |
| isContainer: false, |
| |
| // widgetId: String |
| // a unique, opaque ID string that can be assigned by users or by the |
| // system. If the developer passes an ID which is known not to be |
| // unique, the specified ID is ignored and the system-generated ID is |
| // used instead. |
| widgetId: "", |
| |
| // widgetType: String |
| // used for building generic widgets |
| widgetType: "Widget", |
| |
| // ns: String |
| // defaults to 'dojo'. "namespace" is a reserved word in JavaScript, so we abbreviate |
| ns: "dojo", |
| |
| getNamespacedType: function(){ |
| // summary: |
| // get the "full" name of the widget. If the widget comes from the |
| // "dojo" namespace and is a Button, calling this method will |
| // return "dojo:button", all lower-case |
| return (this.ns ? this.ns + ":" + this.widgetType : this.widgetType).toLowerCase(); // String |
| }, |
| |
| toString: function(){ |
| // summary: |
| // returns a string that represents the widget. When a widget is |
| // cast to a string, this method will be used to generate the |
| // output. Currently, it does not implement any sort of reversable |
| // serialization. |
| return '[Widget ' + this.getNamespacedType() + ', ' + (this.widgetId || 'NO ID') + ']'; // String |
| }, |
| |
| repr: function(){ |
| // summary: returns the string representation of the widget. |
| return this.toString(); // String |
| }, |
| |
| enable: function(){ |
| // summary: |
| // enables the widget, usually involving unmasking inputs and |
| // turning on event handlers. Not implemented here. |
| this.disabled = false; |
| }, |
| |
| disable: function(){ |
| // summary: |
| // disables the widget, usually involves masking inputs and |
| // unsetting event handlers. Not implemented here. |
| this.disabled = true; |
| }, |
| |
| // TODO: |
| // 1) this would be better in HtmlWidget rather than here? |
| // 2) since many widgets don't care if they've been resized, maybe this should be a mixin? |
| onResized: function(){ |
| // summary: |
| // A signal that widgets will call when they have been resized. |
| // Can be connected to for determining if a layout needs to be |
| // reflowed. Clients should override this function to do special |
| // processing, then call this.notifyChildrenOfResize() to notify |
| // children of resize. |
| this.notifyChildrenOfResize(); |
| }, |
| |
| notifyChildrenOfResize: function(){ |
| // summary: dispatches resized events to all children of this widget |
| for(var i=0; i<this.children.length; i++){ |
| var child = this.children[i]; |
| //dojo.debug(this.widgetId + " resizing child " + child.widgetId); |
| if( child.onResized ){ |
| child.onResized(); |
| } |
| } |
| }, |
| |
| create: function(args, fragment, parent, ns){ |
| // summary: |
| // 'create' manages the initialization part of the widget |
| // lifecycle. It's called implicitly when any widget is created. |
| // All other initialization functions for widgets, except for the |
| // constructor, are called as a result of 'create' being fired. |
| // args: Object |
| // a normalized view of the parameters that the widget should take |
| // fragment: Object |
| // if the widget is being instantiated from markup, this object |
| // parent: Widget? |
| // the widget, if any, that this widget will be the child of. If |
| // none is passed, the global default widget is used. |
| // ns: String? |
| // what namespace the widget belongs to |
| // description: |
| // to understand the process by which widgets are instantiated, it |
| // is critical to understand what other methods 'create' calls and |
| // which of them you'll want to over-ride. Of course, adventurous |
| // developers could over-ride 'create' entirely, but this should |
| // only be done as a last resort. |
| // |
| // Below is a list of the methods that are called, in the order |
| // they are fired, along with notes about what they do and if/when |
| // you should over-ride them in your widget: |
| // |
| // mixInProperties: |
| // takes the args and does lightweight type introspection |
| // on pre-existing object properties to initialize widget |
| // values by casting the values that are passed in args |
| // postMixInProperties: |
| // a stub function that you can over-ride to modify |
| // variables that may have been naively assigned by |
| // mixInProperties |
| // # widget is added to manager object here |
| // buildRendering |
| // subclasses use this method to handle all UI initialization |
| // initialize: |
| // a stub function that you can over-ride. |
| // postInitialize: |
| // a stub function that you can over-ride. |
| // postCreate |
| // a stub function that you can over-ride to modify take |
| // actions once the widget has been placed in the UI |
| // |
| // all of these functions are passed the same arguments as are |
| // passed to 'create' |
| |
| //dojo.profile.start(this.widgetType + " create"); |
| if(ns){ |
| this.ns = ns; |
| } |
| // dojo.debug(this.widgetType, "create"); |
| //dojo.profile.start(this.widgetType + " satisfyPropertySets"); |
| this.satisfyPropertySets(args, fragment, parent); |
| //dojo.profile.end(this.widgetType + " satisfyPropertySets"); |
| // dojo.debug(this.widgetType, "-> mixInProperties"); |
| //dojo.profile.start(this.widgetType + " mixInProperties"); |
| this.mixInProperties(args, fragment, parent); |
| //dojo.profile.end(this.widgetType + " mixInProperties"); |
| // dojo.debug(this.widgetType, "-> postMixInProperties"); |
| //dojo.profile.start(this.widgetType + " postMixInProperties"); |
| this.postMixInProperties(args, fragment, parent); |
| //dojo.profile.end(this.widgetType + " postMixInProperties"); |
| // dojo.debug(this.widgetType, "-> dojo.widget.manager.add"); |
| dojo.widget.manager.add(this); |
| // dojo.debug(this.widgetType, "-> buildRendering"); |
| //dojo.profile.start(this.widgetType + " buildRendering"); |
| this.buildRendering(args, fragment, parent); |
| //dojo.profile.end(this.widgetType + " buildRendering"); |
| // dojo.debug(this.widgetType, "-> initialize"); |
| //dojo.profile.start(this.widgetType + " initialize"); |
| this.initialize(args, fragment, parent); |
| //dojo.profile.end(this.widgetType + " initialize"); |
| // dojo.debug(this.widgetType, "-> postInitialize"); |
| // postinitialize includes subcomponent creation |
| // profile is put directly to function |
| this.postInitialize(args, fragment, parent); |
| // dojo.debug(this.widgetType, "-> postCreate"); |
| //dojo.profile.start(this.widgetType + " postCreate"); |
| this.postCreate(args, fragment, parent); |
| //dojo.profile.end(this.widgetType + " postCreate"); |
| // dojo.debug(this.widgetType, "done!"); |
| |
| //dojo.profile.end(this.widgetType + " create"); |
| |
| return this; |
| }, |
| |
| destroy: function(finalize){ |
| // summary: |
| // Destroy this widget and it's descendants. This is the generic |
| // "destructor" function that all widget users should call to |
| // clealy discard with a widget. Once a widget is destroyed, it's |
| // removed from the manager object. |
| // finalize: Boolean |
| // is this function being called part of global environment |
| // tear-down? |
| |
| // FIXME: this is woefully incomplete |
| if(this.parent){ |
| this.parent.removeChild(this); |
| } |
| this.destroyChildren(); |
| this.uninitialize(); |
| this.destroyRendering(finalize); |
| dojo.widget.manager.removeById(this.widgetId); |
| }, |
| |
| destroyChildren: function(){ |
| // summary: |
| // Recursively destroy the children of this widget and their |
| // descendents. |
| var widget; |
| var i=0; |
| while(this.children.length > i){ |
| widget = this.children[i]; |
| if (widget instanceof dojo.widget.Widget) { // find first widget |
| this.removeChild(widget); |
| widget.destroy(); |
| continue; |
| } |
| |
| i++; // skip data object |
| } |
| |
| }, |
| |
| getChildrenOfType: function(/*String*/type, recurse){ |
| // summary: |
| // return an array of descendant widgets who match the passed type |
| // recurse: Boolean |
| // should we try to get all descendants that match? Defaults to |
| // false. |
| var ret = []; |
| var isFunc = dojo.lang.isFunction(type); |
| if(!isFunc){ |
| type = type.toLowerCase(); |
| } |
| for(var x=0; x<this.children.length; x++){ |
| if(isFunc){ |
| if(this.children[x] instanceof type){ |
| ret.push(this.children[x]); |
| } |
| }else{ |
| if(this.children[x].widgetType.toLowerCase() == type){ |
| ret.push(this.children[x]); |
| } |
| } |
| if(recurse){ |
| ret = ret.concat(this.children[x].getChildrenOfType(type, recurse)); |
| } |
| } |
| return ret; // Array |
| }, |
| |
| getDescendants: function(){ |
| // returns: a flattened array of all direct descendants including self |
| var result = []; |
| var stack = [this]; |
| var elem; |
| while ((elem = stack.pop())){ |
| result.push(elem); |
| // a child may be data object without children field set (not widget) |
| if (elem.children) { |
| dojo.lang.forEach(elem.children, function(elem) { stack.push(elem); }); |
| } |
| } |
| return result; // Array |
| }, |
| |
| |
| isFirstChild: function(){ |
| return this === this.parent.children[0]; // Boolean |
| }, |
| |
| isLastChild: function() { |
| return this === this.parent.children[this.parent.children.length-1]; // Boolean |
| }, |
| |
| satisfyPropertySets: function(args){ |
| // summary: not implemented! |
| |
| // dojo.profile.start("satisfyPropertySets"); |
| // get the default propsets for our component type |
| /* |
| var typePropSets = []; // FIXME: need to pull these from somewhere! |
| var localPropSets = []; // pull out propsets from the parser's return structure |
| |
| // for(var x=0; x<args.length; x++){ |
| // } |
| |
| for(var x=0; x<typePropSets.length; x++){ |
| } |
| |
| for(var x=0; x<localPropSets.length; x++){ |
| } |
| */ |
| // dojo.profile.end("satisfyPropertySets"); |
| |
| return args; |
| }, |
| |
| mixInProperties: function(args, /*Object*/frag){ |
| // summary: |
| // takes the list of properties listed in args and sets values of |
| // the current object based on existence of properties with the |
| // same name (case insensitive) and the type of the pre-existing |
| // property. This is a lightweight conversion and is not intended |
| // to capture custom type semantics. |
| // args: Object |
| // A map of properties and values to set on the current object. By |
| // default it is assumed that properties in args are in string |
| // form and need to be converted. However, if there is a |
| // 'fastMixIn' property with the value 'true' in the args param, |
| // this assumption is ignored and all values in args are copied |
| // directly to the current object without any form of type |
| // casting. |
| // description: |
| // The mix-in code attempts to do some type-assignment based on |
| // PRE-EXISTING properties of the "this" object. When a named |
| // property of args is located, it is first tested to make |
| // sure that the current object already "has one". Properties |
| // which are undefined in the base widget are NOT settable here. |
| // The next step is to try to determine type of the pre-existing |
| // property. If it's a string, the property value is simply |
| // assigned. If a function, it is first cast using "new |
| // Function()" and the execution scope modified such that it |
| // always evaluates in the context of the current object. This |
| // listener is then added to the original function via |
| // dojo.event.connect(). If an Array, the system attempts to split |
| // the string value on ";" chars, and no further processing is |
| // attempted (conversion of array elements to a integers, for |
| // instance). If the property value is an Object |
| // (testObj.constructor === Object), the property is split first |
| // on ";" chars, secondly on ":" chars, and the resulting |
| // key/value pairs are assigned to an object in a map style. The |
| // onus is on the property user to ensure that all property values |
| // are converted to the expected type before usage. Properties |
| // which do not occur in the "this" object are assigned to the |
| // this.extraArgs map using both the original name and the |
| // lower-case name of the property. This allows for consistent |
| // access semantics regardless of the case preservation of the |
| // source of the property names. |
| |
| if((args["fastMixIn"])||(frag["fastMixIn"])){ |
| // dojo.profile.start("mixInProperties_fastMixIn"); |
| // fast mix in assumes case sensitivity, no type casting, etc... |
| // dojo.lang.mixin(this, args); |
| for(var x in args){ |
| this[x] = args[x]; |
| } |
| // dojo.profile.end("mixInProperties_fastMixIn"); |
| return; |
| } |
| // dojo.profile.start("mixInProperties"); |
| |
| var undef; |
| |
| // NOTE: we cannot assume that the passed properties are case-correct |
| // (esp due to some browser bugs). Therefore, we attempt to locate |
| // properties for assignment regardless of case. This may cause |
| // problematic assignments and bugs in the future and will need to be |
| // documented with big bright neon lights. |
| |
| // FIXME: fails miserably if a mixin property has a default value of null in |
| // a widget |
| |
| // NOTE: caching lower-cased args in the prototype is only |
| // acceptable if the properties are invariant. |
| // if we have a name-cache, get it |
| var lcArgs = dojo.widget.lcArgsCache[this.widgetType]; |
| if ( lcArgs == null ){ |
| // build a lower-case property name cache if we don't have one |
| lcArgs = {}; |
| for(var y in this){ |
| lcArgs[((new String(y)).toLowerCase())] = y; |
| } |
| dojo.widget.lcArgsCache[this.widgetType] = lcArgs; |
| } |
| var visited = {}; |
| for(var x in args){ |
| if(!this[x]){ // check the cache for properties |
| var y = lcArgs[(new String(x)).toLowerCase()]; |
| if(y){ |
| args[y] = args[x]; |
| x = y; |
| } |
| } |
| if(visited[x]){ continue; } |
| visited[x] = true; |
| if((typeof this[x]) != (typeof undef)){ |
| if(typeof args[x] != "string"){ |
| this[x] = args[x]; |
| }else{ |
| if(dojo.lang.isString(this[x])){ |
| this[x] = args[x]; |
| }else if(dojo.lang.isNumber(this[x])){ |
| this[x] = new Number(args[x]); // FIXME: what if NaN is the result? |
| }else if(dojo.lang.isBoolean(this[x])){ |
| this[x] = (args[x].toLowerCase()=="false") ? false : true; |
| }else if(dojo.lang.isFunction(this[x])){ |
| |
| // FIXME: need to determine if always over-writing instead |
| // of attaching here is appropriate. I suspect that we |
| // might want to only allow attaching w/ action items. |
| |
| // RAR, 1/19/05: I'm going to attach instead of |
| // over-write here. Perhaps function objects could have |
| // some sort of flag set on them? Or mixed-into objects |
| // could have some list of non-mutable properties |
| // (although I'm not sure how that would alleviate this |
| // particular problem)? |
| |
| // this[x] = new Function(args[x]); |
| |
| // after an IRC discussion last week, it was decided |
| // that these event handlers should execute in the |
| // context of the widget, so that the "this" pointer |
| // takes correctly. |
| |
| // argument that contains no punctuation other than . is |
| // considered a function spec, not code |
| if(args[x].search(/[^\w\.]+/i) == -1){ |
| this[x] = dojo.evalObjPath(args[x], false); |
| }else{ |
| var tn = dojo.lang.nameAnonFunc(new Function(args[x]), this); |
| dojo.event.kwConnect({ |
| srcObj: this, |
| srcFunc: x, |
| adviceObj: this, |
| adviceFunc: tn |
| }); |
| } |
| }else if(dojo.lang.isArray(this[x])){ // typeof [] == "object" |
| this[x] = args[x].split(";"); |
| } else if (this[x] instanceof Date) { |
| this[x] = new Date(Number(args[x])); // assume timestamp |
| }else if(typeof this[x] == "object"){ |
| // FIXME: should we be allowing extension here to handle |
| // other object types intelligently? |
| |
| // if a plain string is passed to a property of type dojo.uri.Uri, |
| // we assume it is relative to root of dojo |
| if (this[x] instanceof dojo.uri.Uri){ |
| this[x] = dojo.uri.dojoUri(args[x]); |
| }else{ |
| // FIXME: unlike all other types, we do not replace the |
| // object with a new one here. Should we change that? |
| var pairs = args[x].split(";"); |
| for(var y=0; y<pairs.length; y++){ |
| var si = pairs[y].indexOf(":"); |
| if((si != -1)&&(pairs[y].length>si)){ |
| this[x][pairs[y].substr(0, si).replace(/^\s+|\s+$/g, "")] = pairs[y].substr(si+1); |
| } |
| } |
| } |
| }else{ |
| // the default is straight-up string assignment. When would |
| // we ever hit this? |
| this[x] = args[x]; |
| } |
| } |
| }else{ |
| // collect any extra 'non mixed in' args |
| this.extraArgs[x.toLowerCase()] = args[x]; |
| } |
| } |
| // dojo.profile.end("mixInProperties"); |
| }, |
| |
| postMixInProperties: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ |
| // summary |
| // Called after the parameters to the widget have been read-in, |
| // but before the widget template is instantiated. |
| // Especially useful to set properties that are referenced in the widget template. |
| }, |
| |
| initialize: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ |
| // summary: stub function. |
| return false; |
| // dojo.unimplemented("dojo.widget.Widget.initialize"); |
| }, |
| |
| postInitialize: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ |
| // summary: stub function. |
| return false; |
| }, |
| |
| postCreate: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ |
| // summary: stub function. |
| return false; |
| }, |
| |
| uninitialize: function(){ |
| // summary: |
| // stub function. Over-ride to implement custom widget tear-down |
| // behavior. |
| return false; |
| }, |
| |
| buildRendering: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ |
| // summary: stub function. SUBCLASSES MUST IMPLEMENT |
| dojo.unimplemented("dojo.widget.Widget.buildRendering, on "+this.toString()+", "); |
| return false; |
| }, |
| |
| destroyRendering: function(){ |
| // summary: stub function. SUBCLASSES MUST IMPLEMENT |
| dojo.unimplemented("dojo.widget.Widget.destroyRendering"); |
| return false; |
| }, |
| |
| addedTo: function(parent){ |
| // summary: |
| // stub function this is just a signal that can be caught |
| // parent: Widget |
| // instance of dojo.widget.Widget that we were added to |
| }, |
| |
| addChild: function(child){ |
| // summary: stub function. SUBCLASSES MUST IMPLEMENT |
| dojo.unimplemented("dojo.widget.Widget.addChild"); |
| return false; |
| }, |
| |
| // Detach the given child widget from me, but don't destroy it |
| removeChild: function(/*Widget*/widget){ |
| // summary: |
| // removes the passed widget instance from this widget but does |
| // not destroy it |
| for(var x=0; x<this.children.length; x++){ |
| if(this.children[x] === widget){ |
| this.children.splice(x, 1); |
| widget.parent=null; |
| break; |
| } |
| } |
| return widget; // Widget |
| }, |
| |
| getPreviousSibling: function(){ |
| // summary: |
| // returns null if this is the first child of the parent, |
| // otherwise returns the next sibling to the "left". |
| var idx = this.getParentIndex(); |
| |
| // first node is idx=0 not found is idx<0 |
| if (idx<=0) return null; |
| |
| return this.parent.children[idx-1]; // Widget |
| }, |
| |
| getSiblings: function(){ |
| // summary: gets an array of all children of our parent, including "this" |
| return this.parent.children; // Array |
| }, |
| |
| getParentIndex: function(){ |
| // summary: what index are we at in the parent's children array? |
| return dojo.lang.indexOf(this.parent.children, this, true); // int |
| }, |
| |
| getNextSibling: function(){ |
| // summary: |
| // returns null if this is the last child of the parent, |
| // otherwise returns the next sibling to the "right". |
| |
| var idx = this.getParentIndex(); |
| |
| if (idx == this.parent.children.length-1){return null;} // last node |
| if (idx < 0){return null;} // not found |
| |
| return this.parent.children[idx+1]; // Widget |
| } |
| }); |
| |
| // Lower case name cache: listing of the lower case elements in each widget. |
| // We can't store the lcArgs in the widget itself because if B subclasses A, |
| // then B.prototype.lcArgs might return A.prototype.lcArgs, which is not what we |
| // want |
| dojo.widget.lcArgsCache = {}; |
| |
| // TODO: should have a more general way to add tags or tag libraries? |
| // TODO: need a default tags class to inherit from for things like getting propertySets |
| // TODO: parse properties/propertySets into component attributes |
| // TODO: parse subcomponents |
| // TODO: copy/clone raw markup fragments/nodes as appropriate |
| dojo.widget.tags = {}; |
| dojo.widget.tags.addParseTreeHandler = function(/*String*/type){ |
| // summary: deprecated! |
| dojo.deprecated("addParseTreeHandler", ". ParseTreeHandlers are now reserved for components. Any unfiltered DojoML tag without a ParseTreeHandler is assumed to be a widget", "0.5"); |
| /* |
| var ltype = type.toLowerCase(); |
| this[ltype] = function(fragment, widgetParser, parentComp, insertionIndex, localProps){ |
| var _ltype = ltype; |
| dojo.profile.start(_ltype); |
| var n = dojo.widget.buildWidgetFromParseTree(ltype, fragment, widgetParser, parentComp, insertionIndex, localProps); |
| dojo.profile.end(_ltype); |
| return n; |
| } |
| */ |
| } |
| |
| //dojo.widget.tags.addParseTreeHandler("dojo:widget"); |
| |
| dojo.widget.tags["dojo:propertyset"] = function(fragment, widgetParser, parentComp){ |
| // FIXME: Is this needed? |
| // FIXME: Not sure that this parses into the structure that I want it to parse into... |
| // FIXME: add support for nested propertySets |
| var properties = widgetParser.parseProperties(fragment["dojo:propertyset"]); |
| } |
| |
| // FIXME: need to add the <dojo:connect /> |
| dojo.widget.tags["dojo:connect"] = function(fragment, widgetParser, parentComp){ |
| var properties = widgetParser.parseProperties(fragment["dojo:connect"]); |
| } |
| |
| // FIXME: if we know the insertion point (to a reasonable location), why then do we: |
| // - create a template node |
| // - clone the template node |
| // - render the clone and set properties |
| // - remove the clone from the render tree |
| // - place the clone |
| // this is quite dumb |
| dojo.widget.buildWidgetFromParseTree = function(/*String*/ type, |
| /*Object*/ frag, |
| /*dojo.widget.Parse*/ parser, |
| /*Widget, optional*/ parentComp, |
| /*int, optional*/ insertionIndex, |
| /*Object*/ localProps){ |
| |
| // summary: creates a tree of widgets from the data structure produced by the first-pass parser (frag) |
| |
| |
| var stype = type.split(":"); |
| stype = (stype.length == 2) ? stype[1] : type; |
| |
| // FIXME: we don't seem to be doing anything with this! |
| // var propertySets = parser.getPropertySets(frag); |
| var localProperties = localProps || parser.parseProperties(frag[frag["ns"]+":"+stype]); |
| var twidget = dojo.widget.manager.getImplementation(stype,null,null,frag["ns"]); |
| if(!twidget){ |
| throw new Error('cannot find "' + type + '" widget'); |
| }else if (!twidget.create){ |
| throw new Error('"' + type + '" widget object has no "create" method and does not appear to implement *Widget'); |
| } |
| localProperties["dojoinsertionindex"] = insertionIndex; |
| // FIXME: we lose no less than 5ms in construction! |
| var ret = twidget.create(localProperties, frag, parentComp, frag["ns"]); |
| // dojo.profile.end("buildWidgetFromParseTree"); |
| return ret; |
| } |
| |
| dojo.widget.defineWidget = function(widgetClass, renderer, superclasses, init, props){ |
| // summary: Create a widget constructor function (aka widgetClass) |
| // widgetClass: String |
| // the location in the object hierarchy to place the new widget class constructor |
| // renderer: String |
| // usually "html", determines when this delcaration will be used |
| // superclasses: Function||Function[] |
| // can be either a single function or an array of functions to be |
| // mixed in as superclasses. If an array, only the first will be used |
| // to set prototype inheritance. |
| // init: Function |
| // an optional constructor function. Will be called after superclasses are mixed in. |
| // props: Object |
| // a map of properties and functions to extend the class prototype with |
| |
| // This meta-function does parameter juggling for backward compat and overloading |
| // if 4th argument is a string, we are using the old syntax |
| // old sig: widgetClass, superclasses, props (object), renderer (string), init (function) |
| if(dojo.lang.isString(arguments[3])){ |
| dojo.widget._defineWidget(arguments[0], arguments[3], arguments[1], arguments[4], arguments[2]); |
| }else{ |
| // widgetClass |
| var args = [ arguments[0] ], p = 3; |
| if(dojo.lang.isString(arguments[1])){ |
| // renderer, superclass |
| args.push(arguments[1], arguments[2]); |
| }else{ |
| // superclass |
| args.push('', arguments[1]); |
| p = 2; |
| } |
| if(dojo.lang.isFunction(arguments[p])){ |
| // init (function), props (object) |
| args.push(arguments[p], arguments[p+1]); |
| }else{ |
| // props (object) |
| args.push(null, arguments[p]); |
| } |
| dojo.widget._defineWidget.apply(this, args); |
| } |
| } |
| |
| dojo.widget.defineWidget.renderers = "html|svg|vml"; |
| |
| dojo.widget._defineWidget = function(widgetClass /*string*/, renderer /*string*/, superclasses /*function||array*/, init /*function*/, props /*object*/){ |
| // FIXME: uncomment next line to test parameter juggling ... remove when confidence improves |
| // dojo.debug('(c:)' + widgetClass + '\n\n(r:)' + renderer + '\n\n(i:)' + init + '\n\n(p:)' + props); |
| // widgetClass takes the form foo.bar.baz<.renderer>.WidgetName (e.g. foo.bar.baz.WidgetName or foo.bar.baz.html.WidgetName) |
| var module = widgetClass.split("."); |
| var type = module.pop(); // type <= WidgetName, module <= foo.bar.baz<.renderer> |
| var regx = "\\.(" + (renderer ? renderer + '|' : '') + dojo.widget.defineWidget.renderers + ")\\."; |
| var r = widgetClass.search(new RegExp(regx)); |
| module = (r < 0 ? module.join(".") : widgetClass.substr(0, r)); |
| |
| // deprecated in favor of namespace system, remove for 0.5 |
| dojo.widget.manager.registerWidgetPackage(module); |
| |
| var pos = module.indexOf("."); |
| var nsName = (pos > -1) ? module.substring(0,pos) : module; |
| |
| // FIXME: hrm, this might make things simpler |
| //dojo.widget.tags.addParseTreeHandler(nsName+":"+type.toLowerCase()); |
| |
| props=(props)||{}; |
| props.widgetType = type; |
| if((!init)&&(props["classConstructor"])){ |
| init = props.classConstructor; |
| delete props.classConstructor; |
| } |
| dojo.declare(widgetClass, superclasses, init, props); |
| } |
| |
| dojo.provide("dojo.widget.Parse"); |
| dojo.require("dojo.widget.Manager"); |
| dojo.require("dojo.dom"); |
| |
| // |
| // dojoML parser should be moved out of 'widget', codifying the difference between a 'component' |
| // and a 'widget'. A 'component' being anything that can be generated from a tag. |
| // |
| // a particular dojoML tag would be handled by a registered tagHandler with a hook for a default handler |
| // if the widget system is loaded, a widget builder would be attach itself as the default handler |
| // |
| // widget tags are no longer registered themselves: |
| // they are now arbitrarily namespaced, so we cannot register them all, and the non-prefixed portions |
| // are no longer guaranteed unique |
| // |
| // therefore dojo.widget.tags should go with this parser code out of the widget module |
| // |
| |
| dojo.widget.Parse = function(/*Object*/fragment){ |
| this.propertySetsList = []; |
| this.fragment = fragment; |
| |
| this.createComponents = function(/*Object*/frag, /*Object*/parentComp){ |
| var comps = []; |
| var built = false; |
| // if we have items to parse/create at this level, do it! |
| try{ |
| if(frag && frag.tagName && (frag != frag.nodeRef)){ |
| |
| // these are in fact, not ever for widgets per-se anymore, |
| // but for other markup elements (aka components) |
| var djTags = dojo.widget.tags; |
| |
| // we split so that you can declare multiple |
| // non-destructive components from the same ctor node |
| var tna = String(frag.tagName).split(";"); |
| for(var x=0; x<tna.length; x++){ |
| var ltn = tna[x].replace(/^\s+|\s+$/g, "").toLowerCase(); |
| // FIXME: unsure what this does |
| frag.tagName = ltn; |
| var ret; |
| if(djTags[ltn]){ |
| built = true; |
| ret = djTags[ltn](frag, this, parentComp, frag.index); |
| comps.push(ret); |
| }else{ |
| // we require a namespace prefix, default to dojo: |
| if(ltn.indexOf(":") == -1){ |
| ltn = "dojo:"+ltn; |
| } |
| // FIXME: handling failure condition correctly? |
| // ret = djTags[ltn](frag, this, parentComp, frag.index); |
| ret = dojo.widget.buildWidgetFromParseTree(ltn, frag, this, parentComp, frag.index); |
| if(ret){ |
| built = true; |
| comps.push(ret); |
| } |
| } |
| } |
| } |
| }catch(e){ |
| dojo.debug("dojo.widget.Parse: error:", e); |
| // note, commenting out the next line is breaking several widgets for me |
| // throw e; |
| // IE is such a pain sometimes |
| } |
| // if there's a sub-frag, build widgets from that too |
| if(!built){ |
| comps = comps.concat(this.createSubComponents(frag, parentComp)); |
| } |
| return comps; // Array |
| } |
| |
| this.createSubComponents = function(/*Object*/fragment, /*Object*/parentComp){ |
| // summary: recurses over a raw JavaScript object structure, |
| // and calls the corresponding handler for its normalized tagName if it exists |
| |
| var frag, comps = []; |
| for(var item in fragment){ |
| frag = fragment[item]; |
| if(frag && typeof frag == "object" |
| &&(frag!=fragment.nodeRef) |
| &&(frag!=fragment.tagName) |
| &&(!dojo.dom.isNode(frag))){// needed in IE when we have event.connected to the domNode |
| comps = comps.concat(this.createComponents(frag, parentComp)); |
| } |
| } |
| return comps; // Array |
| } |
| |
| this.parsePropertySets = function(/*Object*/fragment){ |
| // summary: checks the top level of a raw JavaScript object |
| // structure for any propertySets. It stores an array of references to |
| // propertySets that it finds. |
| return []; |
| /* |
| var propertySets = []; |
| for(var item in fragment){ |
| if((fragment[item]["tagName"] == "dojo:propertyset")){ |
| propertySets.push(fragment[item]); |
| } |
| } |
| // FIXME: should we store these propertySets somewhere for later retrieval |
| this.propertySetsList.push(propertySets); |
| return propertySets; |
| */ |
| } |
| |
| this.parseProperties = function(/*Object*/fragment){ |
| // summary: parseProperties checks a raw JavaScript object structure for |
| // properties, and returns a hash of properties that it finds. |
| var properties = {}; |
| for(var item in fragment){ |
| // FIXME: need to check for undefined? |
| // case: its a tagName or nodeRef |
| if((fragment[item] == fragment.tagName)||(fragment[item] == fragment.nodeRef)){ |
| // do nothing |
| }else{ |
| var frag = fragment[item]; |
| if(frag.tagName && dojo.widget.tags[frag.tagName.toLowerCase()]){ |
| // TODO: it isn't a property or property set, it's a fragment, |
| // so do something else |
| // FIXME: needs to be a better/stricter check |
| // TODO: handle xlink:href for external property sets |
| }else if(frag[0] && frag[0].value!="" && frag[0].value!=null){ |
| try{ |
| // FIXME: need to allow more than one provider |
| if(item.toLowerCase() == "dataprovider"){ |
| var _this = this; |
| this.getDataProvider(_this, frag[0].value); |
| properties.dataProvider = this.dataProvider; |
| } |
| properties[item] = frag[0].value; |
| var nestedProperties = this.parseProperties(frag); |
| // FIXME: this kind of copying is expensive and inefficient! |
| for(var property in nestedProperties){ |
| properties[property] = nestedProperties[property]; |
| } |
| }catch(e){ dojo.debug(e); } |
| } |
| switch(item.toLowerCase()){ |
| case "checked": |
| case "disabled": |
| if (typeof properties[item] != "boolean"){ |
| properties[item] = true; |
| } |
| break; |
| } |
| } |
| } |
| return properties; // Object |
| } |
| |
| this.getDataProvider = function(/*Object*/objRef, /*String*/dataUrl){ |
| // FIXME: this is currently sync. To make this async, we made need to move |
| //this step into the widget ctor, so that it is loaded when it is needed |
| // to populate the widget |
| dojo.io.bind({ |
| url: dataUrl, |
| load: function(type, evaldObj){ |
| if(type=="load"){ |
| objRef.dataProvider = evaldObj; |
| } |
| }, |
| mimetype: "text/javascript", |
| sync: true |
| }); |
| } |
| |
| this.getPropertySetById = function(propertySetId){ |
| // summary: returns the propertySet that matches the provided id |
| for(var x = 0; x < this.propertySetsList.length; x++){ |
| if(propertySetId == this.propertySetsList[x]["id"][0].value){ |
| return this.propertySetsList[x]; |
| } |
| } |
| return ""; // String |
| } |
| |
| //FIXME: doesn't use the componentType param? |
| this.getPropertySetsByType = function(componentType){ |
| // summary: returns the propertySet(s) that match(es) the |
| // provided componentClass |
| |
| var propertySets = []; |
| for(var x=0; x < this.propertySetsList.length; x++){ |
| var cpl = this.propertySetsList[x]; |
| var cpcc = cpl.componentClass || cpl.componentType || null; //FIXME: is componentType supposed to be an indirect reference? |
| var propertySetId = this.propertySetsList[x]["id"][0].value; |
| if(cpcc && (propertySetId == cpcc[0].value)){ |
| propertySets.push(cpl); |
| } |
| } |
| return propertySets; // Array |
| } |
| |
| this.getPropertySets = function(/*Object*/fragment){ |
| // summary: returns the propertySet for a given component fragment |
| |
| var ppl = "dojo:propertyproviderlist"; |
| var propertySets = []; |
| var tagname = fragment.tagName; |
| if(fragment[ppl]){ |
| var propertyProviderIds = fragment[ppl].value.split(" "); |
| // FIXME: should the propertyProviderList attribute contain # |
| // syntax for reference to ids or not? |
| // FIXME: need a better test to see if this is local or external |
| // FIXME: doesn't handle nested propertySets, or propertySets that |
| // just contain information about css documents, etc. |
| for(var propertySetId in propertyProviderIds){ |
| if((propertySetId.indexOf("..")==-1)&&(propertySetId.indexOf("://")==-1)){ |
| // get a reference to a propertySet within the current parsed structure |
| var propertySet = this.getPropertySetById(propertySetId); |
| if(propertySet != ""){ |
| propertySets.push(propertySet); |
| } |
| }else{ |
| // FIXME: add code to parse and return a propertySet from |
| // another document |
| // alex: is this even necessaray? Do we care? If so, why? |
| } |
| } |
| } |
| // we put the typed ones first so that the parsed ones override when |
| // iteration happens. |
| return this.getPropertySetsByType(tagname).concat(propertySets); // Array |
| } |
| |
| this.createComponentFromScript = function(/*Node*/nodeRef, /*String*/componentName, /*Object*/properties, /*String?*/ns){ |
| // summary: |
| // nodeRef: the node to be replaced... in the future, we might want to add |
| // an alternative way to specify an insertion point |
| // componentName: the expected dojo widget name, i.e. Button of ContextMenu |
| // properties: an object of name value pairs |
| // ns: the namespace of the widget. Defaults to "dojo" |
| |
| properties.fastMixIn = true; |
| // FIXME: we pulled it apart and now we put it back together ... |
| var ltn = (ns || "dojo") + ":" + componentName.toLowerCase(); |
| if(dojo.widget.tags[ltn]){ |
| return [dojo.widget.tags[ltn](properties, this, null, null, properties)]; // Array |
| } |
| return [dojo.widget.buildWidgetFromParseTree(ltn, properties, this, null, null, properties)]; // Array |
| } |
| } |
| |
| dojo.widget._parser_collection = {"dojo": new dojo.widget.Parse() }; |
| |
| dojo.widget.getParser = function(/*String?*/name){ |
| if(!name){ name = "dojo"; } |
| if(!this._parser_collection[name]){ |
| this._parser_collection[name] = new dojo.widget.Parse(); |
| } |
| return this._parser_collection[name]; |
| } |
| |
| dojo.widget.createWidget = function(/*String*/name, /*String*/props, /*Node*/refNode, /*String*/position){ |
| // summary: Creates widget |
| // name: The name of the widget to create with optional namespace prefix, |
| // e.g."ns:widget", namespace defaults to "dojo". |
| // props: Key-Value pairs of properties of the widget |
| // refNode: If the position argument is specified, this node is used as |
| // a reference for inserting this node into a DOM tree; else |
| // the widget becomes the domNode |
| // position: The position to insert this widget's node relative to the |
| // refNode argument |
| |
| var isNode = false; |
| var isNameStr = (typeof name == "string"); |
| if(isNameStr){ |
| var pos = name.indexOf(":"); |
| var ns = (pos > -1) ? name.substring(0,pos) : "dojo"; |
| if(pos > -1){ name = name.substring(pos+1); } |
| var lowerCaseName = name.toLowerCase(); |
| var namespacedName = ns + ":" + lowerCaseName; |
| isNode = (dojo.byId(name) && !dojo.widget.tags[namespacedName]); |
| } |
| |
| if((arguments.length == 1) && (isNode || !isNameStr)){ |
| // we got a DOM node |
| var xp = new dojo.xml.Parse(); |
| // FIXME: we should try to find the parent! |
| var tn = isNode ? dojo.byId(name) : name; |
| return dojo.widget.getParser().createComponents(xp.parseElement(tn, null, true))[0]; |
| } |
| |
| function fromScript(placeKeeperNode, name, props, ns){ |
| props[namespacedName] = { |
| dojotype: [{value: lowerCaseName}], |
| nodeRef: placeKeeperNode, |
| fastMixIn: true |
| }; |
| props.ns = ns; |
| return dojo.widget.getParser().createComponentFromScript(placeKeeperNode, name, props, ns); |
| } |
| |
| props = props||{}; |
| var notRef = false; |
| var tn = null; |
| var h = dojo.render.html.capable; |
| if(h){ |
| tn = document.createElement("span"); |
| } |
| if(!refNode){ |
| notRef = true; |
| refNode = tn; |
| if(h){ |
| dojo.body().appendChild(refNode); |
| } |
| }else if(position){ |
| dojo.dom.insertAtPosition(tn, refNode, position); |
| }else{ // otherwise don't replace, but build in-place |
| tn = refNode; |
| } |
| var widgetArray = fromScript(tn, name.toLowerCase(), props, ns); |
| if( (!widgetArray)||(!widgetArray[0])|| |
| (typeof widgetArray[0].widgetType == "undefined") ){ |
| throw new Error("createWidget: Creation of \"" + name + "\" widget failed."); |
| } |
| try{ |
| if(notRef && widgetArray[0].domNode.parentNode){ |
| widgetArray[0].domNode.parentNode.removeChild(widgetArray[0].domNode); |
| } |
| }catch(e){ |
| /* squelch for Safari */ |
| dojo.debug(e); |
| } |
| return widgetArray[0]; // Widget |
| } |
| |
| dojo.kwCompoundRequire({ |
| common: [["dojo.uri.Uri", false, false]] |
| }); |
| dojo.provide("dojo.uri.*"); |
| |
| dojo.provide("dojo.widget.DomWidget"); |
| |
| dojo.require("dojo.event.*"); |
| |
| dojo.require("dojo.dom"); |
| dojo.require("dojo.html.style"); |
| |
| |
| dojo.require("dojo.lang.func"); |
| dojo.require("dojo.lang.extras"); |
| |
| dojo.widget._cssFiles = {}; |
| dojo.widget._cssStrings = {}; |
| dojo.widget._templateCache = {}; |
| |
| dojo.widget.defaultStrings = { |
| // summary: a mapping of strings that are used in template variable replacement |
| dojoRoot: dojo.hostenv.getBaseScriptUri(), |
| dojoWidgetModuleUri: dojo.uri.moduleUri("dojo.widget"), |
| baseScriptUri: dojo.hostenv.getBaseScriptUri() |
| }; |
| |
| dojo.widget.fillFromTemplateCache = function(obj, templatePath, templateString, avoidCache){ |
| // summary: |
| // static method to build from a template w/ or w/o a real widget in |
| // place |
| // obj: DomWidget |
| // an instance of dojo.widget.DomWidget to initialize the template for |
| // templatePath: String |
| // the URL to get the template from. dojo.uri.Uri is often passed as well. |
| // templateString: String? |
| // a string to use in lieu of fetching the template from a URL |
| // avoidCache: Boolean? |
| // should the template system not use whatever is in the cache and |
| // always use the passed templatePath or templateString? |
| |
| // dojo.debug("avoidCache:", avoidCache); |
| var tpath = templatePath || obj.templatePath; |
| |
| var tmplts = dojo.widget._templateCache; |
| if(!tpath && !obj["widgetType"]) { // don't have a real template here |
| do { |
| var dummyName = "__dummyTemplate__" + dojo.widget._templateCache.dummyCount++; |
| } while(tmplts[dummyName]); |
| obj.widgetType = dummyName; |
| } |
| var wt = tpath?tpath.toString():obj.widgetType; |
| |
| var ts = tmplts[wt]; |
| if(!ts){ |
| tmplts[wt] = {"string": null, "node": null}; |
| if(avoidCache){ |
| ts = {}; |
| }else{ |
| ts = tmplts[wt]; |
| } |
| } |
| |
| if((!obj.templateString)&&(!avoidCache)){ |
| obj.templateString = templateString || ts["string"]; |
| } |
| if(obj.templateString){ |
| obj.templateString = this._sanitizeTemplateString(obj.templateString); |
| } |
| |
| if((!obj.templateNode)&&(!avoidCache)){ |
| obj.templateNode = ts["node"]; |
| } |
| if((!obj.templateNode)&&(!obj.templateString)&&(tpath)){ |
| // fetch a text fragment and assign it to templateString |
| // NOTE: we rely on blocking IO here! |
| var tstring = this._sanitizeTemplateString(dojo.hostenv.getText(tpath)); |
| |
| obj.templateString = tstring; |
| if(!avoidCache){ |
| tmplts[wt]["string"] = tstring; |
| } |
| } |
| if((!ts["string"])&&(!avoidCache)){ |
| ts.string = obj.templateString; |
| } |
| } |
| |
| dojo.widget._sanitizeTemplateString = function(/*String*/tString){ |
| //summary: Strips <?xml ...?> declarations so that external SVG and XML |
| //documents can be added to a document without worry. Also, if the string |
| //is an HTML document, only the part inside the body tag is returned. |
| if(tString){ |
| tString = tString.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, ""); |
| var matches = tString.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); |
| if(matches){ |
| tString = matches[1]; |
| } |
| }else{ |
| tString = ""; |
| } |
| return tString; //String |
| } |
| |
| dojo.widget._templateCache.dummyCount = 0; |
| |
| // Array: list of properties to search for node-to-property mappings |
| dojo.widget.attachProperties = ["dojoAttachPoint", "id"]; |
| |
| // String: name of the property to use for mapping DOM events to widget functions |
| dojo.widget.eventAttachProperty = "dojoAttachEvent"; |
| |
| // String: property name of code to evaluate when the widget is constructed |
| dojo.widget.onBuildProperty = "dojoOnBuild"; |
| |
| // Array: possible accessibility values to set on widget elements - role or state |
| dojo.widget.waiNames = ["waiRole", "waiState"]; |
| |
| dojo.widget.wai = { |
| // summary: Contains functions to set accessibility roles and states |
| // onto widget elements |
| waiRole: { |
| // name: String: |
| // information for mapping accessibility role |
| name: "waiRole", |
| // namespace: String: |
| // URI of the namespace for the set of roles |
| "namespace": "http://www.w3.org/TR/xhtml2", |
| // alias: String: |
| // The alias to assign the namespace |
| alias: "x2", |
| // prefix: String: |
| // The prefix to assign to the role value |
| prefix: "wairole:" |
| }, |
| waiState: { |
| // name: String: |
| // information for mapping accessibility state |
| name: "waiState", |
| // namespace: String: |
| // URI of the namespace for the set of states |
| "namespace": "http://www.w3.org/2005/07/aaa", |
| // alias: String: |
| // The alias to assign the namespace |
| alias: "aaa", |
| // prefix: String: |
| // empty string - state value does not require prefix |
| prefix: "" |
| }, |
| setAttr: function(/*DomNode*/node, /*String*/ ns, /*String*/ attr, /*String|Boolean*/value){ |
| // summary: Use appropriate API to set the role or state attribute onto the element. |
| // description: In IE use the generic setAttribute() api. Append a namespace |
| // alias to the attribute name and appropriate prefix to the value. |
| // Otherwise, use the setAttribueNS api to set the namespaced attribute. Also |
| // add the appropriate prefix to the attribute value. |
| if(dojo.render.html.ie){ |
| node.setAttribute(this[ns].alias+":"+ attr, this[ns].prefix+value); |
| }else{ |
| node.setAttributeNS(this[ns]["namespace"], attr, this[ns].prefix+value); |
| } |
| }, |
| |
| getAttr: function(/*DomNode*/ node, /*String*/ ns, /*String|Boolena*/ attr){ |
| // Summary: Use the appropriate API to retrieve the role or state value |
| // Description: In IE use the generic getAttribute() api. An alias value |
| // was added to the attribute name to simulate a namespace when the attribute |
| // was set. Otherwise use the getAttributeNS() api to retrieve the state value |
| if(dojo.render.html.ie){ |
| return node.getAttribute(this[ns].alias+":"+attr); |
| }else{ |
| return node.getAttributeNS(this[ns]["namespace"], attr); |
| } |
| }, |
| removeAttr: function(/*DomNode*/ node, /*String*/ ns, /*String|Boolena*/ attr){ |
| // summary: Use the appropriate API to remove the role or state value |
| // description: In IE use the generic removeAttribute() api. An alias value |
| // was added to the attribute name to simulate a namespace when the attribute |
| // was set. Otherwise use the removeAttributeNS() api to remove the state value |
| var success = true; //only IE returns a value |
| if(dojo.render.html.ie){ |
| success = node.removeAttribute(this[ns].alias+":"+attr); |
| }else{ |
| node.removeAttributeNS(this[ns]["namespace"], attr); |
| } |
| return success; |
| } |
| }; |
| |
| dojo.widget.attachTemplateNodes = function(rootNode, /*Widget*/ targetObj, events ){ |
| // summary: |
| // map widget properties and functions to the handlers specified in |
| // the dom node and it's descendants. This function iterates over all |
| // nodes and looks for these properties: |
| // * dojoAttachPoint |
| // * dojoAttachEvent |
| // * waiRole |
| // * waiState |
| // * any "dojoOn*" proprties passed in the events array |
| // rootNode: DomNode |
| // the node to search for properties. All children will be searched. |
| // events: Array |
| // a list of properties generated from getDojoEventsFromStr. |
| |
| // FIXME: this method is still taking WAAAY too long. We need ways of optimizing: |
| // a.) what we are looking for on each node |
| // b.) the nodes that are subject to interrogation (use xpath instead?) |
| // c.) how expensive event assignment is (less eval(), more connect()) |
| // var start = new Date(); |
| var elementNodeType = dojo.dom.ELEMENT_NODE; |
| |
| function trim(str){ |
| return str.replace(/^\s+|\s+$/g, ""); |
| } |
| |
| if(!rootNode){ |
| rootNode = targetObj.domNode; |
| } |
| |
| if(rootNode.nodeType != elementNodeType){ |
| return; |
| } |
| // alert(events.length); |
| |
| var nodes = rootNode.all || rootNode.getElementsByTagName("*"); |
| var _this = targetObj; |
| for(var x=-1; x<nodes.length; x++){ |
| var baseNode = (x == -1) ? rootNode : nodes[x]; |
| // FIXME: is this going to have capitalization problems? Could use getAttribute(name, 0); to get attributes case-insensitve |
| var attachPoint = []; |
| if(!targetObj.widgetsInTemplate || !baseNode.getAttribute('dojoType')){ |
| for(var y=0; y<this.attachProperties.length; y++){ |
| var tmpAttachPoint = baseNode.getAttribute(this.attachProperties[y]); |
| if(tmpAttachPoint){ |
| attachPoint = tmpAttachPoint.split(";"); |
| for(var z=0; z<attachPoint.length; z++){ |
| if(dojo.lang.isArray(targetObj[attachPoint[z]])){ |
| targetObj[attachPoint[z]].push(baseNode); |
| }else{ |
| targetObj[attachPoint[z]]=baseNode; |
| } |
| } |
| break; |
| } |
| } |
| |
| var attachEvent = baseNode.getAttribute(this.eventAttachProperty); |
| if(attachEvent){ |
| // NOTE: we want to support attributes that have the form |
| // "domEvent: nativeEvent; ..." |
| var evts = attachEvent.split(";"); |
| for(var y=0; y<evts.length; y++){ |
| if((!evts[y])||(!evts[y].length)){ continue; } |
| var thisFunc = null; |
| var tevt = trim(evts[y]); |
| if(evts[y].indexOf(":") >= 0){ |
| // oh, if only JS had tuple assignment |
| var funcNameArr = tevt.split(":"); |
| tevt = trim(funcNameArr[0]); |
| thisFunc = trim(funcNameArr[1]); |
| } |
| if(!thisFunc){ |
| thisFunc = tevt; |
| } |
| |
| var tf = function(){ |
| var ntf = new String(thisFunc); |
| return function(evt){ |
| if(_this[ntf]){ |
| _this[ntf](dojo.event.browser.fixEvent(evt, this)); |
| } |
| }; |
| }(); |
| dojo.event.browser.addListener(baseNode, tevt, tf, false, true); |
| // dojo.event.browser.addListener(baseNode, tevt, dojo.lang.hitch(_this, thisFunc)); |
| } |
| } |
| |
| for(var y=0; y<events.length; y++){ |
| //alert(events[x]); |
| var evtVal = baseNode.getAttribute(events[y]); |
| if((evtVal)&&(evtVal.length)){ |
| var thisFunc = null; |
| var domEvt = events[y].substr(4); // clober the "dojo" prefix |
| thisFunc = trim(evtVal); |
| var funcs = [thisFunc]; |
| if(thisFunc.indexOf(";")>=0){ |
| funcs = dojo.lang.map(thisFunc.split(";"), trim); |
| } |
| for(var z=0; z<funcs.length; z++){ |
| if(!funcs[z].length){ continue; } |
| var tf = function(){ |
| var ntf = new String(funcs[z]); |
| return function(evt){ |
| if(_this[ntf]){ |
| _this[ntf](dojo.event.browser.fixEvent(evt, this)); |
| } |
| } |
| }(); |
| dojo.event.browser.addListener(baseNode, domEvt, tf, false, true); |
| // dojo.event.browser.addListener(baseNode, domEvt, dojo.lang.hitch(_this, funcs[z])); |
| } |
| } |
| } |
| } |
| // continue; |
| |
| // FIXME: we need to put this into some kind of lookup structure |
| // instead of direct assignment |
| var tmpltPoint = baseNode.getAttribute(this.templateProperty); |
| if(tmpltPoint){ |
| targetObj[tmpltPoint]=baseNode; |
| } |
| |
| dojo.lang.forEach(dojo.widget.waiNames, function(name){ |
| var wai = dojo.widget.wai[name]; |
| var val = baseNode.getAttribute(wai.name); |
| if(val){ |
| if(val.indexOf('-') == -1){ |
| dojo.widget.wai.setAttr(baseNode, wai.name, "role", val); |
| }else{ |
| // this is a state-value pair |
| var statePair = val.split('-'); |
| dojo.widget.wai.setAttr(baseNode, wai.name, statePair[0], statePair[1]); |
| } |
| } |
| }, this); |
| |
| var onBuild = baseNode.getAttribute(this.onBuildProperty); |
| if(onBuild){ |
| eval("var node = baseNode; var widget = targetObj; "+onBuild); |
| } |
| } |
| |
| } |
| |
| dojo.widget.getDojoEventsFromStr = function(str){ |
| // summary: |
| // generates a list of properties with names that match the form |
| // dojoOn* |
| // str: String |
| // the template string to search |
| |
| // var lstr = str.toLowerCase(); |
| var re = /(dojoOn([a-z]+)(\s?))=/gi; |
| var evts = str ? str.match(re)||[] : []; |
| var ret = []; |
| var lem = {}; |
| for(var x=0; x<evts.length; x++){ |
| if(evts[x].length < 1){ continue; } |
| var cm = evts[x].replace(/\s/, ""); |
| cm = (cm.slice(0, cm.length-1)); |
| if(!lem[cm]){ |
| lem[cm] = true; |
| ret.push(cm); |
| } |
| } |
| return ret; // Array |
| } |
| |
| dojo.declare("dojo.widget.DomWidget", |
| dojo.widget.Widget, |
| function(){ |
| // summary: |
| // dojo.widget.DomWidget is the superclass that provides behavior for all |
| // DOM-based renderers, including HtmlWidget and SvgWidget. DomWidget |
| // implements the templating system that most widget authors use to define |
| // the UI for their widgets. |
| if((arguments.length>0)&&(typeof arguments[0] == "object")){ |
| this.create(arguments[0]); |
| } |
| }, |
| { |
| // templateNode: DomNode |
| // a node that represents the widget template. Pre-empts both templateString and templatePath. |
| templateNode: null, |
| |
| // templateString String: |
| // a string that represents the widget template. Pre-empts the |
| // templatePath. In builds that have their strings "interned", the |
| // templatePath is converted to an inline templateString, thereby |
| // preventing a synchronous network call. |
| templateString: null, |
| |
| // templateCssString String: |
| // a string that represents the CSS for the widgettemplate. |
| // Pre-empts the templateCssPath. In builds that have their |
| // strings "interned", the templateCssPath is converted to an |
| // inline templateCssString, thereby preventing a synchronous |
| // network call. |
| templateCssString: null, |
| |
| // preventClobber Boolean: |
| // should the widget not replace the node from which it was |
| // constructed? Widgets that apply behaviors to pre-existing parts |
| // of a page can be implemented easily by setting this to "true". |
| // In these cases, the domNode property will point to the node |
| // which the widget was created from. |
| preventClobber: false, |
| |
| // domNode DomNode: |
| // this is our visible representation of the widget! Other DOM |
| // Nodes may by assigned to other properties, usually through the |
| // template system's dojoAttachPonit syntax, but the domNode |
| // property is the canonical "top level" node in widget UI. |
| domNode: null, |
| |
| // containerNode DomNode: |
| // holds child elements. "containerNode" is generally set via a |
| // dojoAttachPoint assignment and it designates where widgets that |
| // are defined as "children" of the parent will be placed |
| // visually. |
| containerNode: null, |
| |
| // widgetsInTemplate Boolean: |
| // should we parse the template to find widgets that might be |
| // declared in markup inside it? false by default. |
| widgetsInTemplate: false, |
| |
| addChild: function(/*Widget*/ widget, overrideContainerNode, pos, ref, insertIndex){ |
| // summary: |
| // Process the given child widget, inserting it's dom node as |
| // a child of our dom node |
| // overrideContainerNode: DomNode? |
| // a non-default container node for the widget |
| // pos: String? |
| // can be one of "before", "after", "first", or "last". This |
| // has the same meaning as in dojo.dom.insertAtPosition() |
| // ref: DomNode? |
| // a node to place the widget relative to |
| // insertIndex: int? |
| // DOM index, same meaning as in dojo.dom.insertAtIndex() |
| // returns: the widget that was inserted |
| |
| // FIXME: should we support addition at an index in the children arr and |
| // order the display accordingly? Right now we always append. |
| if(!this.isContainer){ // we aren't allowed to contain other widgets, it seems |
| dojo.debug("dojo.widget.DomWidget.addChild() attempted on non-container widget"); |
| return null; |
| }else{ |
| if(insertIndex == undefined){ |
| insertIndex = this.children.length; |
| } |
| this.addWidgetAsDirectChild(widget, overrideContainerNode, pos, ref, insertIndex); |
| this.registerChild(widget, insertIndex); |
| } |
| return widget; // Widget |
| }, |
| |
| addWidgetAsDirectChild: function(/*Widget*/ widget, overrideContainerNode, pos, ref, insertIndex){ |
| // summary: |
| // Process the given child widget, inserting it's dom node as |
| // a child of our dom node |
| // overrideContainerNode: DomNode |
| // a non-default container node for the widget |
| // pos: String? |
| // can be one of "before", "after", "first", or "last". This |
| // has the same meaning as in dojo.dom.insertAtPosition() |
| // ref: DomNode? |
| // a node to place the widget relative to |
| // insertIndex: int? |
| // DOM index, same meaning as in dojo.dom.insertAtIndex() |
| if((!this.containerNode)&&(!overrideContainerNode)){ |
| this.containerNode = this.domNode; |
| } |
| var cn = (overrideContainerNode) ? overrideContainerNode : this.containerNode; |
| if(!pos){ pos = "after"; } |
| if(!ref){ |
| if(!cn){ cn = dojo.body(); } |
| ref = cn.lastChild; |
| } |
| if(!insertIndex) { insertIndex = 0; } |
| widget.domNode.setAttribute("dojoinsertionindex", insertIndex); |
| |
| // insert the child widget domNode directly underneath my domNode, in the |
| // specified position (by default, append to end) |
| if(!ref){ |
| cn.appendChild(widget.domNode); |
| }else{ |
| // FIXME: was this meant to be the (ugly hack) way to support insert @ index? |
| //dojo.dom[pos](widget.domNode, ref, insertIndex); |
| |
| // CAL: this appears to be the intended way to insert a node at a given position... |
| if (pos == 'insertAtIndex'){ |
| // dojo.debug("idx:", insertIndex, "isLast:", ref === cn.lastChild); |
| dojo.dom.insertAtIndex(widget.domNode, ref.parentNode, insertIndex); |
| }else{ |
| // dojo.debug("pos:", pos, "isLast:", ref === cn.lastChild); |
| if((pos == "after")&&(ref === cn.lastChild)){ |
| cn.appendChild(widget.domNode); |
| }else{ |
| dojo.dom.insertAtPosition(widget.domNode, cn, pos); |
| } |
| } |
| } |
| }, |
| |
| registerChild: function(widget, insertionIndex){ |
| // summary: record that given widget descends from me |
| // widget: Widget |
| // the widget that is now a child |
| // insertionIndex: int |
| // where in the children[] array to place it |
| |
| // we need to insert the child at the right point in the parent's |
| // 'children' array, based on the insertionIndex |
| |
| widget.dojoInsertionIndex = insertionIndex; |
| |
| var idx = -1; |
| for(var i=0; i<this.children.length; i++){ |
| |
| //This appears to fix an out of order issue in the case of mixed |
| //markup and programmatically added children. Previously, if a child |
| //existed from markup, and another child was addChild()d without specifying |
| //any additional parameters, it would end up first in the list, when in fact |
| //it should be after. I can't see cases where this would break things, but |
| //I could see no other obvious solution. -dustin |
| |
| if (this.children[i].dojoInsertionIndex <= insertionIndex){ |
| idx = i; |
| } |
| } |
| |
| this.children.splice(idx+1, 0, widget); |
| |
| widget.parent = this; |
| widget.addedTo(this, idx+1); |
| |
| // If this widget was created programatically, then it was erroneously added |
| // to dojo.widget.manager.topWidgets. Fix that here. |
| delete dojo.widget.manager.topWidgets[widget.widgetId]; |
| }, |
| |
| removeChild: function(/*Widget*/ widget){ |
| // summary: detach child domNode from parent domNode |
| dojo.dom.removeNode(widget.domNode); |
| |
| // remove child widget from parent widget |
| return dojo.widget.DomWidget.superclass.removeChild.call(this, widget); // Widget |
| }, |
| |
| getFragNodeRef: function(frag){ |
| // summary: |
| // returns the source node, if any, that the widget was |
| // declared from |
| // frag: Object |
| // an opaque data structure generated by the first-pass parser |
| if(!frag){return null;} // null |
| if(!frag[this.getNamespacedType()]){ |
| dojo.raise("Error: no frag for widget type " + this.getNamespacedType() |
| + ", id " + this.widgetId |
| + " (maybe a widget has set it's type incorrectly)"); |
| } |
| return frag[this.getNamespacedType()]["nodeRef"]; // DomNode |
| }, |
| |
| postInitialize: function(/*Object*/ args, /*Object*/ frag, /*Widget*/ parentComp){ |
| // summary: |
| // Replace the source domNode with the generated dom |
| // structure, and register the widget with its parent. |
| // This is an implementation of the stub function defined in |
| // dojo.widget.Widget. |
| |
| //dojo.profile.start(this.widgetType + " postInitialize"); |
| |
| var sourceNodeRef = this.getFragNodeRef(frag); |
| // Stick my generated dom into the output tree |
| //alert(this.widgetId + ": replacing " + sourceNodeRef + " with " + this.domNode.innerHTML); |
| if (parentComp && (parentComp.snarfChildDomOutput || !sourceNodeRef)){ |
| // Add my generated dom as a direct child of my parent widget |
| // This is important for generated widgets, and also cases where I am generating an |
| // <li> node that can't be inserted back into the original DOM tree |
| parentComp.addWidgetAsDirectChild(this, "", "insertAtIndex", "", args["dojoinsertionindex"], sourceNodeRef); |
| } else if (sourceNodeRef){ |
| // Do in-place replacement of the my source node with my generated dom |
| if(this.domNode && (this.domNode !== sourceNodeRef)){ |
| this._sourceNodeRef = dojo.dom.replaceNode(sourceNodeRef, this.domNode); |
| } |
| } |
| |
| // Register myself with my parent, or with the widget manager if |
| // I have no parent |
| // TODO: the code below erroneously adds all programatically generated widgets |
| // to topWidgets (since we don't know who the parent is until after creation finishes) |
| if ( parentComp ) { |
| parentComp.registerChild(this, args.dojoinsertionindex); |
| } else { |
| dojo.widget.manager.topWidgets[this.widgetId]=this; |
| } |
| |
| if(this.widgetsInTemplate){ |
| var parser = new dojo.xml.Parse(); |
| |
| var subContainerNode; |
| //TODO: use xpath here? |
| var subnodes = this.domNode.getElementsByTagName("*"); |
| for(var i=0;i<subnodes.length;i++){ |
| if(subnodes[i].getAttribute('dojoAttachPoint') == 'subContainerWidget'){ |
| subContainerNode = subnodes[i]; |
| // break; |
| } |
| if(subnodes[i].getAttribute('dojoType')){ |
| subnodes[i].setAttribute('isSubWidget', true); |
| } |
| } |
| if (this.isContainer && !this.containerNode){ |
| //no containerNode is available, which means a widget is used as a container. find it here and move |
| //all dom nodes defined in the main html page as children of this.domNode into the actual container |
| //widget's node (at this point, the subwidgets defined in the template file is not parsed yet) |
| if(subContainerNode){ |
| var src = this.getFragNodeRef(frag); |
| if (src){ |
| dojo.dom.moveChildren(src, subContainerNode); |
| //do not need to follow children nodes in the main html page, as they |
| //will be dealt with in the subContainerWidget |
| frag['dojoDontFollow'] = true; |
| } |
| }else{ |
| dojo.debug("No subContainerWidget node can be found in template file for widget "+this); |
| } |
| } |
| |
| var templatefrag = parser.parseElement(this.domNode, null, true); |
| // createSubComponents not createComponents because frag has already been created |
| dojo.widget.getParser().createSubComponents(templatefrag, this); |
| |
| //find all the sub widgets defined in the template file of this widget |
| var subwidgets = []; |
| var stack = [this]; |
| var w; |
| while((w = stack.pop())){ |
| for(var i = 0; i < w.children.length; i++){ |
| var cwidget = w.children[i]; |
| if(cwidget._processedSubWidgets || !cwidget.extraArgs['issubwidget']){ continue; } |
| subwidgets.push(cwidget); |
| if(cwidget.isContainer){ |
| stack.push(cwidget); |
| } |
| } |
| } |
| |
| //connect event to this widget/attach dom node |
| for(var i = 0; i < subwidgets.length; i++){ |
| var widget = subwidgets[i]; |
| if(widget._processedSubWidgets){ |
| dojo.debug("This should not happen: widget._processedSubWidgets is already true!"); |
| return; |
| } |
| widget._processedSubWidgets = true; |
| if(widget.extraArgs['dojoattachevent']){ |
| var evts = widget.extraArgs['dojoattachevent'].split(";"); |
| for(var j=0; j<evts.length; j++){ |
| var thisFunc = null; |
| var tevt = dojo.string.trim(evts[j]); |
| if(tevt.indexOf(":") >= 0){ |
| // oh, if only JS had tuple assignment |
| var funcNameArr = tevt.split(":"); |
| tevt = dojo.string.trim(funcNameArr[0]); |
| thisFunc = dojo.string.trim(funcNameArr[1]); |
| } |
| if(!thisFunc){ |
| thisFunc = tevt; |
| } |
| if(dojo.lang.isFunction(widget[tevt])){ |
| dojo.event.kwConnect({ |
| srcObj: widget, |
| srcFunc: tevt, |
| targetObj: this, |
| targetFunc: thisFunc |
| }); |
| }else{ |
| alert(tevt+" is not a function in widget "+widget); |
| } |
| } |
| } |
| |
| if(widget.extraArgs['dojoattachpoint']){ |
| //don't attach widget.domNode here, as we do not know which |
| //dom node we should connect to (in checkbox widget case, |
| //it is inputNode). So we make the widget itself available |
| this[widget.extraArgs['dojoattachpoint']] = widget; |
| } |
| } |
| } |
| |
| //dojo.profile.end(this.widgetType + " postInitialize"); |
| |
| // Expand my children widgets |
| /* dojoDontFollow is important for a very special case |
| * basically if you have a widget that you instantiate from script |
| * and that widget is a container, and it contains a reference to a parent |
| * instance, the parser will start recursively parsing until the browser |
| * complains. So the solution is to set an initialization property of |
| * dojoDontFollow: true and then it won't recurse where it shouldn't |
| */ |
| if(this.isContainer && !frag["dojoDontFollow"]){ |
| //alert("recurse from " + this.widgetId); |
| // build any sub-components with us as the parent |
| dojo.widget.getParser().createSubComponents(frag, this); |
| } |
| }, |
| |
| // method over-ride |
| buildRendering: function(/*Object*/ args, /*Object*/ frag){ |
| // summary: |
| // Construct the UI for this widget, generally from a |
| // template. This can be over-ridden for custom UI creation to |
| // to side-step the template system. This is an |
| // implementation of the stub function defined in |
| // dojo.widget.Widget. |
| |
| // DOM widgets construct themselves from a template |
| var ts = dojo.widget._templateCache[this.widgetType]; |
| |
| // Handle style for this widget here, as even if templatePath |
| // is not set, style specified by templateCssString or templateCssPath |
| // should be applied. templateCssString has higher priority |
| // than templateCssPath |
| if(args["templatecsspath"]){ |
| args["templateCssPath"] = args["templatecsspath"]; |
| } |
| var cpath = args["templateCssPath"] || this.templateCssPath; |
| if(cpath && !dojo.widget._cssFiles[cpath.toString()]){ |
| if((!this.templateCssString)&&(cpath)){ |
| this.templateCssString = dojo.hostenv.getText(cpath); |
| this.templateCssPath = null; |
| } |
| dojo.widget._cssFiles[cpath.toString()] = true; |
| } |
| |
| if((this["templateCssString"])&&(!dojo.widget._cssStrings[this.templateCssString])){ |
| dojo.html.insertCssText(this.templateCssString, null, cpath); |
| dojo.widget._cssStrings[this.templateCssString] = true; |
| } |
| if( |
| (!this.preventClobber)&&( |
| (this.templatePath)|| |
| (this.templateNode)|| |
| ( |
| (this["templateString"])&&(this.templateString.length) |
| )|| |
| ( |
| (typeof ts != "undefined")&&( (ts["string"])||(ts["node"]) ) |
| ) |
| ) |
| ){ |
| // if it looks like we can build the thing from a template, do it! |
| this.buildFromTemplate(args, frag); |
| }else{ |
| // otherwise, assign the DOM node that was the source of the widget |
| // parsing to be the root node |
| this.domNode = this.getFragNodeRef(frag); |
| } |
| this.fillInTemplate(args, frag); // this is where individual widgets |
| // will handle population of data |
| // from properties, remote data |
| // sets, etc. |
| }, |
| |
| buildFromTemplate: function(/*Object*/ args, /*Object*/ frag){ |
| // summary: |
| // Called by buildRendering, creates the actual UI in a DomWidget. |
| |
| // var start = new Date(); |
| // copy template properties if they're already set in the templates object |
| // dojo.debug("buildFromTemplate:", this); |
| var avoidCache = false; |
| if(args["templatepath"]){ |
| // avoidCache = true; |
| args["templatePath"] = args["templatepath"]; |
| } |
| dojo.widget.fillFromTemplateCache( this, |
| args["templatePath"], |
| null, |
| avoidCache); |
| var ts = dojo.widget._templateCache[this.templatePath?this.templatePath.toString():this.widgetType]; |
| if((ts)&&(!avoidCache)){ |
| if(!this.templateString.length){ |
| this.templateString = ts["string"]; |
| } |
| if(!this.templateNode){ |
| this.templateNode = ts["node"]; |
| } |
| } |
| var matches = false; |
| var node = null; |
| // var tstr = new String(this.templateString); |
| var tstr = this.templateString; |
| // attempt to clone a template node, if there is one |
| if((!this.templateNode)&&(this.templateString)){ |
| matches = this.templateString.match(/\$\{([^\}]+)\}/g); |
| if(matches) { |
| // if we do property replacement, don't create a templateNode |
| // to clone from. |
| var hash = this.strings || {}; |
| // FIXME: should this hash of default replacements be cached in |
| // templateString? |
| for(var key in dojo.widget.defaultStrings) { |
| if(dojo.lang.isUndefined(hash[key])) { |
| hash[key] = dojo.widget.defaultStrings[key]; |
| } |
| } |
| // FIXME: this is a lot of string munging. Can we make it faster? |
| for(var i = 0; i < matches.length; i++) { |
| var key = matches[i]; |
| key = key.substring(2, key.length-1); |
| var kval = (key.substring(0, 5) == "this.") ? dojo.lang.getObjPathValue(key.substring(5), this) : hash[key]; |
| var value; |
| if((kval)||(dojo.lang.isString(kval))){ |
| value = new String((dojo.lang.isFunction(kval)) ? kval.call(this, key, this.templateString) : kval); |
| // Safer substitution, see heading "Attribute values" in |
| // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 |
| while (value.indexOf("\"") > -1) { |
| value=value.replace("\"","""); |
| } |
| tstr = tstr.replace(matches[i], value); |
| } |
| } |
| }else{ |
| // otherwise, we are required to instantiate a copy of the template |
| // string if one is provided. |
| |
| // FIXME: need to be able to distinguish here what should be done |
| // or provide a generic interface across all DOM implementations |
| // FIMXE: this breaks if the template has whitespace as its first |
| // characters |
| // node = this.createNodesFromText(this.templateString, true); |
| // this.templateNode = node[0].cloneNode(true); // we're optimistic here |
| this.templateNode = this.createNodesFromText(this.templateString, true)[0]; |
| if(!avoidCache){ |
| ts.node = this.templateNode; |
| } |
| } |
| } |
| if((!this.templateNode)&&(!matches)){ |
| dojo.debug("DomWidget.buildFromTemplate: could not create template"); |
| return false; |
| }else if(!matches){ |
| node = this.templateNode.cloneNode(true); |
| if(!node){ return false; } |
| }else{ |
| node = this.createNodesFromText(tstr, true)[0]; |
| } |
| |
| // recurse through the node, looking for, and attaching to, our |
| // attachment points which should be defined on the template node. |
| |
| this.domNode = node; |
| // dojo.profile.start("attachTemplateNodes"); |
| this.attachTemplateNodes(); |
| // dojo.profile.end("attachTemplateNodes"); |
| |
| // relocate source contents to templated container node |
| // this.containerNode must be able to receive children, or exceptions will be thrown |
| if (this.isContainer && this.containerNode){ |
| var src = this.getFragNodeRef(frag); |
| if (src){ |
| dojo.dom.moveChildren(src, this.containerNode); |
| } |
| } |
| }, |
| |
| attachTemplateNodes: function(baseNode, targetObj){ |
| // summary: |
| // hooks up event handlers and property/node linkages. Calls |
| // dojo.widget.attachTemplateNodes to do all the hard work. |
| // baseNode: DomNode |
| // defaults to "this.domNode" |
| // targetObj: Widget |
| // defaults to "this" |
| if(!baseNode){ baseNode = this.domNode; } |
| if(!targetObj){ targetObj = this; } |
| return dojo.widget.attachTemplateNodes(baseNode, targetObj, |
| dojo.widget.getDojoEventsFromStr(this.templateString)); |
| }, |
| |
| fillInTemplate: function(){ |
| // summary: |
| // stub function! sub-classes may use as a default UI |
| // initializer function. The UI rendering will be available by |
| // the time this is called from buildRendering. If |
| // buildRendering is over-ridden, this function may not be |
| // fired! |
| |
| // dojo.unimplemented("dojo.widget.DomWidget.fillInTemplate"); |
| }, |
| |
| // method over-ride |
| destroyRendering: function(){ |
| // summary: UI destructor. Destroy the dom nodes associated w/this widget. |
| try{ |
| dojo.dom.destroyNode(this.domNode); |
| delete this.domNode; |
| }catch(e){ /* squelch! */ } |
| if(this._sourceNodeRef){ |
| try{ |
| dojo.dom.destroyNode(this._sourceNodeRef); |
| }catch(e){ /* squelch! */ } |
| } |
| }, |
| |
| createNodesFromText: function(){ |
| // summary |
| // Attempts to create a set of nodes based on the structure of the passed text. |
| // Implemented in HtmlWidget and SvgWidget. |
| dojo.unimplemented("dojo.widget.DomWidget.createNodesFromText"); |
| } |
| } |
| ); |
| |
| dojo.provide("dojo.html.layout"); |
| |
| dojo.require("dojo.html.common"); |
| dojo.require("dojo.html.style"); |
| dojo.require("dojo.html.display"); |
| |
| dojo.html.sumAncestorProperties = function(/* HTMLElement */node, /* string */prop){ |
| // summary |
| // Returns the sum of the passed property on all ancestors of node. |
| node = dojo.byId(node); |
| if(!node){ return 0; } // FIXME: throw an error? |
| |
| var retVal = 0; |
| while(node){ |
| if(dojo.html.getComputedStyle(node, 'position') == 'fixed'){ |
| return 0; |
| } |
| var val = node[prop]; |
| if(val){ |
| retVal += val - 0; |
| if(node==dojo.body()){ break; }// opera and khtml #body & #html has the same values, we only need one value |
| } |
| node = node.parentNode; |
| } |
| return retVal; // integer |
| } |
| |
| dojo.html.setStyleAttributes = function(/* HTMLElement */node, /* string */attributes) { |
| // summary |
| // allows a dev to pass a string similar to what you'd pass in style="", and apply it to a node. |
| node = dojo.byId(node); |
| var splittedAttribs=attributes.replace(/(;)?\s*$/, "").split(";"); |
| for(var i=0; i<splittedAttribs.length; i++){ |
| var nameValue=splittedAttribs[i].split(":"); |
| var name=nameValue[0].replace(/\s*$/, "").replace(/^\s*/, "").toLowerCase(); |
| var value=nameValue[1].replace(/\s*$/, "").replace(/^\s*/, ""); |
| switch(name){ |
| case "opacity": |
| dojo.html.setOpacity(node, value); |
| break; |
| case "content-height": |
| dojo.html.setContentBox(node, {height: value}); |
| break; |
| case "content-width": |
| dojo.html.setContentBox(node, {width: value}); |
| break; |
| case "outer-height": |
| dojo.html.setMarginBox(node, {height: value}); |
| break; |
| case "outer-width": |
| dojo.html.setMarginBox(node, {width: value}); |
| break; |
| default: |
| node.style[dojo.html.toCamelCase(name)]=value; |
| } |
| } |
| } |
| |
| dojo.html.boxSizing = { |
| MARGIN_BOX: "margin-box", |
| BORDER_BOX: "border-box", |
| PADDING_BOX: "padding-box", |
| CONTENT_BOX: "content-box" |
| }; |
| |
| dojo.html.getAbsolutePosition = dojo.html.abs = function(/* HTMLElement */node, /* boolean? */includeScroll, /* string? */boxType){ |
| // summary |
| // Gets the absolute position of the passed element based on the document itself. |
| node = dojo.byId(node, node.ownerDocument); |
| var ret = { |
| x: 0, |
| y: 0 |
| }; |
| |
| var bs = dojo.html.boxSizing; |
| if(!boxType) { boxType = bs.CONTENT_BOX; } |
| var nativeBoxType = 2; //BORDER box |
| var targetBoxType; |
| switch(boxType){ |
| case bs.MARGIN_BOX: |
| targetBoxType = 3; |
| break; |
| case bs.BORDER_BOX: |
| targetBoxType = 2; |
| break; |
| case bs.PADDING_BOX: |
| default: |
| targetBoxType = 1; |
| break; |
| case bs.CONTENT_BOX: |
| targetBoxType = 0; |
| break; |
| } |
| |
| var h = dojo.render.html; |
| var db = document["body"]||document["documentElement"]; |
| |
| if(h.ie){ |
| with(node.getBoundingClientRect()){ |
| ret.x = left-2; |
| ret.y = top-2; |
| } |
| }else if(document.getBoxObjectFor){ |
| // mozilla |
| nativeBoxType = 1; //getBoxObjectFor return padding box coordinate |
| try{ |
| var bo = document.getBoxObjectFor(node); |
| ret.x = bo.x - dojo.html.sumAncestorProperties(node, "scrollLeft"); |
| ret.y = bo.y - dojo.html.sumAncestorProperties(node, "scrollTop"); |
| }catch(e){ |
| // squelch |
| } |
| }else{ |
| if(node["offsetParent"]){ |
| var endNode; |
| // in Safari, if the node is an absolutely positioned child of |
| // the body and the body has a margin the offset of the child |
| // and the body contain the body's margins, so we need to end |
| // at the body |
| if( (h.safari)&& |
| (node.style.getPropertyValue("position") == "absolute")&& |
| (node.parentNode == db)){ |
| endNode = db; |
| }else{ |
| endNode = db.parentNode; |
| } |
| |
| //TODO: set correct nativeBoxType for safari/konqueror |
| |
| if(node.parentNode != db){ |
| var nd = node; |
| if(dojo.render.html.opera){ nd = db; } |
| ret.x -= dojo.html.sumAncestorProperties(nd, "scrollLeft"); |
| ret.y -= dojo.html.sumAncestorProperties(nd, "scrollTop"); |
| } |
| var curnode = node; |
| do{ |
| var n = curnode["offsetLeft"]; |
| //FIXME: ugly hack to workaround the submenu in |
| //popupmenu2 does not shown up correctly in opera. |
| //Someone have a better workaround? |
| if(!h.opera || n>0){ |
| ret.x += isNaN(n) ? 0 : n; |
| } |
| var m = curnode["offsetTop"]; |
| ret.y += isNaN(m) ? 0 : m; |
| curnode = curnode.offsetParent; |
| }while((curnode != endNode)&&(curnode != null)); |
| }else if(node["x"]&&node["y"]){ |
| ret.x += isNaN(node.x) ? 0 : node.x; |
| ret.y += isNaN(node.y) ? 0 : node.y; |
| } |
| } |
| |
| // account for document scrolling! |
| if(includeScroll){ |
| var scroll = dojo.html.getScroll(); |
| ret.y += scroll.top; |
| ret.x += scroll.left; |
| } |
| |
| var extentFuncArray=[dojo.html.getPaddingExtent, dojo.html.getBorderExtent, dojo.html.getMarginExtent]; |
| if(nativeBoxType > targetBoxType){ |
| for(var i=targetBoxType;i<nativeBoxType;++i){ |
| ret.y += extentFuncArray[i](node, 'top'); |
| ret.x += extentFuncArray[i](node, 'left'); |
| } |
| }else if(nativeBoxType < targetBoxType){ |
| for(var i=targetBoxType;i>nativeBoxType;--i){ |
| ret.y -= extentFuncArray[i-1](node, 'top'); |
| ret.x -= extentFuncArray[i-1](node, 'left'); |
| } |
| } |
| ret.top = ret.y; |
| ret.left = ret.x; |
| return ret; // object |
| } |
| |
| dojo.html.isPositionAbsolute = function(/* HTMLElement */node){ |
| // summary |
| // Returns true if the element is absolutely positioned. |
| return (dojo.html.getComputedStyle(node, 'position') == 'absolute'); // boolean |
| } |
| |
| dojo.html._sumPixelValues = function(/* HTMLElement */node, selectors, autoIsZero){ |
| var total = 0; |
| for(var x=0; x<selectors.length; x++){ |
| total += dojo.html.getPixelValue(node, selectors[x], autoIsZero); |
| } |
| return total; |
| } |
| |
| dojo.html.getMargin = function(/* HTMLElement */node){ |
| // summary |
| // Returns the width and height of the passed node's margin |
| return { |
| width: dojo.html._sumPixelValues(node, ["margin-left", "margin-right"], (dojo.html.getComputedStyle(node, 'position') == 'absolute')), |
| height: dojo.html._sumPixelValues(node, ["margin-top", "margin-bottom"], (dojo.html.getComputedStyle(node, 'position') == 'absolute')) |
| }; // object |
| } |
| |
| dojo.html.getBorder = function(/* HTMLElement */node){ |
| // summary |
| // Returns the width and height of the passed node's border |
| return { |
| width: dojo.html.getBorderExtent(node, 'left') + dojo.html.getBorderExtent(node, 'right'), |
| height: dojo.html.getBorderExtent(node, 'top') + dojo.html.getBorderExtent(node, 'bottom') |
| }; // object |
| } |
| |
| dojo.html.getBorderExtent = function(/* HTMLElement */node, /* string */side){ |
| // summary |
| // returns the width of the requested border |
| return (dojo.html.getStyle(node, 'border-' + side + '-style') == 'none' ? 0 : dojo.html.getPixelValue(node, 'border-' + side + '-width')); // integer |
| } |
| |
| dojo.html.getMarginExtent = function(/* HTMLElement */node, /* string */side){ |
| // summary |
| // returns the width of the requested margin |
| return dojo.html._sumPixelValues(node, ["margin-" + side], dojo.html.isPositionAbsolute(node)); // integer |
| } |
| |
| dojo.html.getPaddingExtent = function(/* HTMLElement */node, /* string */side){ |
| // summary |
| // Returns the width of the requested padding |
| return dojo.html._sumPixelValues(node, ["padding-" + side], true); // integer |
| } |
| |
| dojo.html.getPadding = function(/* HTMLElement */node){ |
| // summary |
| // Returns the width and height of the passed node's padding |
| return { |
| width: dojo.html._sumPixelValues(node, ["padding-left", "padding-right"], true), |
| height: dojo.html._sumPixelValues(node, ["padding-top", "padding-bottom"], true) |
| }; // object |
| } |
| |
| dojo.html.getPadBorder = function(/* HTMLElement */node){ |
| // summary |
| // Returns the width and height of the passed node's padding and border |
| var pad = dojo.html.getPadding(node); |
| var border = dojo.html.getBorder(node); |
| return { width: pad.width + border.width, height: pad.height + border.height }; // object |
| } |
| |
| dojo.html.getBoxSizing = function(/* HTMLElement */node){ |
| // summary |
| // Returns which box model the passed element is working with |
| var h = dojo.render.html; |
| var bs = dojo.html.boxSizing; |
| if(((h.ie)||(h.opera)) && node.nodeName.toLowerCase() != "img"){ |
| var cm = document["compatMode"]; |
| if((cm == "BackCompat")||(cm == "QuirksMode")){ |
| return bs.BORDER_BOX; // string |
| }else{ |
| return bs.CONTENT_BOX; // string |
| } |
| }else{ |
| if(arguments.length == 0){ node = document.documentElement; } |
| var sizing; |
| if(!h.ie){ |
| sizing = dojo.html.getStyle(node, "-moz-box-sizing"); |
| if(!sizing){ |
| sizing = dojo.html.getStyle(node, "box-sizing"); |
| } |
| } |
| return (sizing ? sizing : bs.CONTENT_BOX); // string |
| } |
| } |
| |
| dojo.html.isBorderBox = function(/* HTMLElement */node){ |
| // summary |
| // returns whether the passed element is using border box sizing or not. |
| return (dojo.html.getBoxSizing(node) == dojo.html.boxSizing.BORDER_BOX); // boolean |
| } |
| |
| dojo.html.getBorderBox = function(/* HTMLElement */node){ |
| // summary |
| // Returns the dimensions of the passed element based on border-box sizing. |
| node = dojo.byId(node); |
| return { width: node.offsetWidth, height: node.offsetHeight }; // object |
| } |
| |
| dojo.html.getPaddingBox = function(/* HTMLElement */node){ |
| // summary |
| // Returns the dimensions of the padding box (see http://www.w3.org/TR/CSS21/box.html) |
| var box = dojo.html.getBorderBox(node); |
| var border = dojo.html.getBorder(node); |
| return { |
| width: box.width - border.width, |
| height:box.height - border.height |
| }; // object |
| } |
| |
| dojo.html.getContentBox = function(/* HTMLElement */node){ |
| // summary |
| // Returns the dimensions of the content box (see http://www.w3.org/TR/CSS21/box.html) |
| node = dojo.byId(node); |
| var padborder = dojo.html.getPadBorder(node); |
| return { |
| width: node.offsetWidth - padborder.width, |
| height: node.offsetHeight - padborder.height |
| }; // object |
| } |
| |
| dojo.html.setContentBox = function(/* HTMLElement */node, /* object */args){ |
| // summary |
| // Sets the dimensions of the passed node according to content sizing. |
| node = dojo.byId(node); |
| var width = 0; var height = 0; |
| var isbb = dojo.html.isBorderBox(node); |
| var padborder = (isbb ? dojo.html.getPadBorder(node) : { width: 0, height: 0}); |
| var ret = {}; |
| if(typeof args.width != "undefined"){ |
| width = args.width + padborder.width; |
| ret.width = dojo.html.setPositivePixelValue(node, "width", width); |
| } |
| if(typeof args.height != "undefined"){ |
| height = args.height + padborder.height; |
| ret.height = dojo.html.setPositivePixelValue(node, "height", height); |
| } |
| return ret; // object |
| } |
| |
| dojo.html.getMarginBox = function(/* HTMLElement */node){ |
| // summary |
| // returns the dimensions of the passed node including any margins. |
| var borderbox = dojo.html.getBorderBox(node); |
| var margin = dojo.html.getMargin(node); |
| return { width: borderbox.width + margin.width, height: borderbox.height + margin.height }; // object |
| } |
| |
| dojo.html.setMarginBox = function(/* HTMLElement */node, /* object */args){ |
| // summary |
| // Sets the dimensions of the passed node using margin box calcs. |
| node = dojo.byId(node); |
| var width = 0; var height = 0; |
| var isbb = dojo.html.isBorderBox(node); |
| var padborder = (!isbb ? dojo.html.getPadBorder(node) : { width: 0, height: 0 }); |
| var margin = dojo.html.getMargin(node); |
| var ret = {}; |
| if(typeof args.width != "undefined"){ |
| width = args.width - padborder.width; |
| width -= margin.width; |
| ret.width = dojo.html.setPositivePixelValue(node, "width", width); |
| } |
| if(typeof args.height != "undefined"){ |
| height = args.height - padborder.height; |
| height -= margin.height; |
| ret.height = dojo.html.setPositivePixelValue(node, "height", height); |
| } |
| return ret; // object |
| } |
| |
| dojo.html.getElementBox = function(/* HTMLElement */node, /* string */type){ |
| // summary |
| // return dimesions of a node based on the passed box model type. |
| var bs = dojo.html.boxSizing; |
| switch(type){ |
| case bs.MARGIN_BOX: |
| return dojo.html.getMarginBox(node); // object |
| case bs.BORDER_BOX: |
| return dojo.html.getBorderBox(node); // object |
| case bs.PADDING_BOX: |
| return dojo.html.getPaddingBox(node); // object |
| case bs.CONTENT_BOX: |
| default: |
| return dojo.html.getContentBox(node); // object |
| } |
| } |
| // in: coordinate array [x,y,w,h] or dom node |
| // return: coordinate object |
| dojo.html.toCoordinateObject = dojo.html.toCoordinateArray = function(/* array */coords, /* boolean? */includeScroll, /* string? */boxtype) { |
| // summary |
| // Converts an array of coordinates into an object of named arguments. |
| if(coords instanceof Array || typeof coords == "array"){ |
| dojo.deprecated("dojo.html.toCoordinateArray", "use dojo.html.toCoordinateObject({left: , top: , width: , height: }) instead", "0.5"); |
| // coords is already an array (of format [x,y,w,h]), just return it |
| while ( coords.length < 4 ) { coords.push(0); } |
| while ( coords.length > 4 ) { coords.pop(); } |
| var ret = { |
| left: coords[0], |
| top: coords[1], |
| width: coords[2], |
| height: coords[3] |
| }; |
| }else if(!coords.nodeType && !(coords instanceof String || typeof coords == "string") && |
| ('width' in coords || 'height' in coords || 'left' in coords || |
| 'x' in coords || 'top' in coords || 'y' in coords)){ |
| // coords is a coordinate object or at least part of one |
| var ret = { |
| left: coords.left||coords.x||0, |
| top: coords.top||coords.y||0, |
| width: coords.width||0, |
| height: coords.height||0 |
| }; |
| }else{ |
| // coords is an dom object (or dom object id); return it's coordinates |
| var node = dojo.byId(coords); |
| var pos = dojo.html.abs(node, includeScroll, boxtype); |
| var marginbox = dojo.html.getMarginBox(node); |
| var ret = { |
| left: pos.left, |
| top: pos.top, |
| width: marginbox.width, |
| height: marginbox.height |
| }; |
| } |
| ret.x = ret.left; |
| ret.y = ret.top; |
| return ret; // object |
| } |
| |
| dojo.html.setMarginBoxWidth = dojo.html.setOuterWidth = function(node, width){ |
| return dojo.html._callDeprecated("setMarginBoxWidth", "setMarginBox", arguments, "width"); |
| } |
| dojo.html.setMarginBoxHeight = dojo.html.setOuterHeight = function(){ |
| return dojo.html._callDeprecated("setMarginBoxHeight", "setMarginBox", arguments, "height"); |
| } |
| dojo.html.getMarginBoxWidth = dojo.html.getOuterWidth = function(){ |
| return dojo.html._callDeprecated("getMarginBoxWidth", "getMarginBox", arguments, null, "width"); |
| } |
| dojo.html.getMarginBoxHeight = dojo.html.getOuterHeight = function(){ |
| return dojo.html._callDeprecated("getMarginBoxHeight", "getMarginBox", arguments, null, "height"); |
| } |
| dojo.html.getTotalOffset = function(node, type, includeScroll){ |
| return dojo.html._callDeprecated("getTotalOffset", "getAbsolutePosition", arguments, null, type); |
| } |
| dojo.html.getAbsoluteX = function(node, includeScroll){ |
| return dojo.html._callDeprecated("getAbsoluteX", "getAbsolutePosition", arguments, null, "x"); |
| } |
| dojo.html.getAbsoluteY = function(node, includeScroll){ |
| return dojo.html._callDeprecated("getAbsoluteY", "getAbsolutePosition", arguments, null, "y"); |
| } |
| dojo.html.totalOffsetLeft = function(node, includeScroll){ |
| return dojo.html._callDeprecated("totalOffsetLeft", "getAbsolutePosition", arguments, null, "left"); |
| } |
| dojo.html.totalOffsetTop = function(node, includeScroll){ |
| return dojo.html._callDeprecated("totalOffsetTop", "getAbsolutePosition", arguments, null, "top"); |
| } |
| dojo.html.getMarginWidth = function(node){ |
| return dojo.html._callDeprecated("getMarginWidth", "getMargin", arguments, null, "width"); |
| } |
| dojo.html.getMarginHeight = function(node){ |
| return dojo.html._callDeprecated("getMarginHeight", "getMargin", arguments, null, "height"); |
| } |
| dojo.html.getBorderWidth = function(node){ |
| return dojo.html._callDeprecated("getBorderWidth", "getBorder", arguments, null, "width"); |
| } |
| dojo.html.getBorderHeight = function(node){ |
| return dojo.html._callDeprecated("getBorderHeight", "getBorder", arguments, null, "height"); |
| } |
| dojo.html.getPaddingWidth = function(node){ |
| return dojo.html._callDeprecated("getPaddingWidth", "getPadding", arguments, null, "width"); |
| } |
| dojo.html.getPaddingHeight = function(node){ |
| return dojo.html._callDeprecated("getPaddingHeight", "getPadding", arguments, null, "height"); |
| } |
| dojo.html.getPadBorderWidth = function(node){ |
| return dojo.html._callDeprecated("getPadBorderWidth", "getPadBorder", arguments, null, "width"); |
| } |
| dojo.html.getPadBorderHeight = function(node){ |
| return dojo.html._callDeprecated("getPadBorderHeight", "getPadBorder", arguments, null, "height"); |
| } |
| dojo.html.getBorderBoxWidth = dojo.html.getInnerWidth = function(){ |
| return dojo.html._callDeprecated("getBorderBoxWidth", "getBorderBox", arguments, null, "width"); |
| } |
| dojo.html.getBorderBoxHeight = dojo.html.getInnerHeight = function(){ |
| return dojo.html._callDeprecated("getBorderBoxHeight", "getBorderBox", arguments, null, "height"); |
| } |
| dojo.html.getContentBoxWidth = dojo.html.getContentWidth = function(){ |
| return dojo.html._callDeprecated("getContentBoxWidth", "getContentBox", arguments, null, "width"); |
| } |
| dojo.html.getContentBoxHeight = dojo.html.getContentHeight = function(){ |
| return dojo.html._callDeprecated("getContentBoxHeight", "getContentBox", arguments, null, "height"); |
| } |
| dojo.html.setContentBoxWidth = dojo.html.setContentWidth = function(node, width){ |
| return dojo.html._callDeprecated("setContentBoxWidth", "setContentBox", arguments, "width"); |
| } |
| dojo.html.setContentBoxHeight = dojo.html.setContentHeight = function(node, height){ |
| return dojo.html._callDeprecated("setContentBoxHeight", "setContentBox", arguments, "height"); |
| } |
| |
| dojo.provide("dojo.html.util"); |
| |
| |
| dojo.html.getElementWindow = function(/* HTMLElement */element){ |
| // summary |
| // Get the window object where the element is placed in. |
| return dojo.html.getDocumentWindow( element.ownerDocument ); // Window |
| } |
| |
| dojo.html.getDocumentWindow = function(doc){ |
| // summary |
| // Get window object associated with document doc |
| |
| // With Safari, there is not wa to retrieve the window from the document, so we must fix it. |
| if(dojo.render.html.safari && !doc._parentWindow){ |
| /* |
| This is a Safari specific function that fix the reference to the parent |
| window from the document object. |
| */ |
| |
| var fix=function(win){ |
| win.document._parentWindow=win; |
| for(var i=0; i<win.frames.length; i++){ |
| fix(win.frames[i]); |
| } |
| } |
| fix(window.top); |
| } |
| |
| //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.render.html.ie && window !== document.parentWindow && !doc._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.parentWindow || doc.defaultView; // Window |
| } |
| |
| dojo.html.gravity = function(/* HTMLElement */node, /* DOMEvent */e){ |
| // summary |
| // Calculates the mouse's direction of gravity relative to the centre |
| // of the given node. |
| // <p> |
| // If you wanted to insert a node into a DOM tree based on the mouse |
| // position you might use the following code: |
| // <pre> |
| // if (gravity(node, e) & gravity.NORTH) { [insert before]; } |
| // else { [insert after]; } |
| // </pre> |
| // |
| // @param node The node |
| // @param e The event containing the mouse coordinates |
| // @return The directions, NORTH or SOUTH and EAST or WEST. These |
| // are properties of the function. |
| node = dojo.byId(node); |
| var mouse = dojo.html.getCursorPosition(e); |
| |
| with (dojo.html) { |
| var absolute = getAbsolutePosition(node, true); |
| var bb = getBorderBox(node); |
| var nodecenterx = absolute.x + (bb.width / 2); |
| var nodecentery = absolute.y + (bb.height / 2); |
| } |
| |
| with (dojo.html.gravity) { |
| return ((mouse.x < nodecenterx ? WEST : EAST) | (mouse.y < nodecentery ? NORTH : SOUTH)); // integer |
| } |
| } |
| |
| dojo.html.gravity.NORTH = 1; |
| dojo.html.gravity.SOUTH = 1 << 1; |
| dojo.html.gravity.EAST = 1 << 2; |
| dojo.html.gravity.WEST = 1 << 3; |
| |
| dojo.html.overElement = function(/* HTMLElement */element, /* DOMEvent */e){ |
| // summary |
| // Returns whether the mouse is over the passed element. |
| // Element must be display:block (ie, not a <span>) |
| element = dojo.byId(element); |
| var mouse = dojo.html.getCursorPosition(e); |
| var bb = dojo.html.getBorderBox(element); |
| var absolute = dojo.html.getAbsolutePosition(element, true, dojo.html.boxSizing.BORDER_BOX); |
| var top = absolute.y; |
| var bottom = top + bb.height; |
| var left = absolute.x; |
| var right = left + bb.width; |
| |
| return (mouse.x >= left |
| && mouse.x <= right |
| && mouse.y >= top |
| && mouse.y <= bottom |
| ); // boolean |
| } |
| |
| dojo.html.renderedTextContent = function(/* HTMLElement */node){ |
| // summary |
| // Attempts to return the text as it would be rendered, with the line breaks |
| // sorted out nicely. Unfinished. |
| node = dojo.byId(node); |
| var result = ""; |
| if (node == null) { return result; } |
| for (var i = 0; i < node.childNodes.length; i++) { |
| switch (node.childNodes[i].nodeType) { |
| case 1: // ELEMENT_NODE |
| case 5: // ENTITY_REFERENCE_NODE |
| var display = "unknown"; |
| try { |
| display = dojo.html.getStyle(node.childNodes[i], "display"); |
| } catch(E) {} |
| switch (display) { |
| case "block": case "list-item": case "run-in": |
| case "table": case "table-row-group": case "table-header-group": |
| case "table-footer-group": case "table-row": case "table-column-group": |
| case "table-column": case "table-cell": case "table-caption": |
| // TODO: this shouldn't insert double spaces on aligning blocks |
| result += "\n"; |
| result += dojo.html.renderedTextContent(node.childNodes[i]); |
| result += "\n"; |
| break; |
| |
| case "none": break; |
| |
| default: |
| if(node.childNodes[i].tagName && node.childNodes[i].tagName.toLowerCase() == "br") { |
| result += "\n"; |
| } else { |
| result += dojo.html.renderedTextContent(node.childNodes[i]); |
| } |
| break; |
| } |
| break; |
| case 3: // TEXT_NODE |
| case 2: // ATTRIBUTE_NODE |
| case 4: // CDATA_SECTION_NODE |
| var text = node.childNodes[i].nodeValue; |
| var textTransform = "unknown"; |
| try { |
| textTransform = dojo.html.getStyle(node, "text-transform"); |
| } catch(E) {} |
| switch (textTransform){ |
| case "capitalize": |
| var words = text.split(' '); |
| for(var i=0; i<words.length; i++){ |
| words[i] = words[i].charAt(0).toUpperCase() + words[i].substring(1); |
| } |
| text = words.join(" "); |
| break; |
| case "uppercase": text = text.toUpperCase(); break; |
| case "lowercase": text = text.toLowerCase(); break; |
| default: break; // leave as is |
| } |
| // TODO: implement |
| switch (textTransform){ |
| case "nowrap": break; |
| case "pre-wrap": break; |
| case "pre-line": break; |
| case "pre": break; // leave as is |
| default: |
| // remove whitespace and collapse first space |
| text = text.replace(/\s+/, " "); |
| if (/\s$/.test(result)) { text.replace(/^\s/, ""); } |
| break; |
| } |
| result += text; |
| break; |
| default: |
| break; |
| } |
| } |
| return result; // string |
| } |
| |
| dojo.html.createNodesFromText = function(/* string */txt, /* boolean? */trim){ |
| // summary |
| // Attempts to create a set of nodes based on the structure of the passed text. |
| if(trim) { txt = txt.replace(/^\s+|\s+$/g, ""); } |
| |
| var tn = dojo.doc().createElement("div"); |
| // tn.style.display = "none"; |
| tn.style.visibility= "hidden"; |
| dojo.body().appendChild(tn); |
| var tableType = "none"; |
| if((/^<t[dh][\s\r\n>]/i).test(txt.replace(/^\s+/))) { |
| txt = "<table><tbody><tr>" + txt + "</tr></tbody></table>"; |
| tableType = "cell"; |
| } else if((/^<tr[\s\r\n>]/i).test(txt.replace(/^\s+/))) { |
| txt = "<table><tbody>" + txt + "</tbody></table>"; |
| tableType = "row"; |
| } else if((/^<(thead|tbody|tfoot)[\s\r\n>]/i).test(txt.replace(/^\s+/))) { |
| txt = "<table>" + txt + "</table>"; |
| tableType = "section"; |
| } |
| tn.innerHTML = txt; |
| if(tn["normalize"]){ |
| tn.normalize(); |
| } |
| |
| var parent = null; |
| switch(tableType) { |
| case "cell": |
| parent = tn.getElementsByTagName("tr")[0]; |
| break; |
| case "row": |
| parent = tn.getElementsByTagName("tbody")[0]; |
| break; |
| case "section": |
| parent = tn.getElementsByTagName("table")[0]; |
| break; |
| default: |
| parent = tn; |
| break; |
| } |
| |
| /* this doesn't make much sense, I'm assuming it just meant trim() so wrap was replaced with trim |
| if(wrap){ |
| var ret = []; |
| // start hack |
| var fc = tn.firstChild; |
| ret[0] = ((fc.nodeValue == " ")||(fc.nodeValue == "\t")) ? fc.nextSibling : fc; |
| // end hack |
| // tn.style.display = "none"; |
| dojo.body().removeChild(tn); |
| return ret; |
| } |
| */ |
| var nodes = []; |
| for(var x=0; x<parent.childNodes.length; x++){ |
| nodes.push(parent.childNodes[x].cloneNode(true)); |
| } |
| tn.style.display = "none"; // FIXME: why do we do this? |
| dojo.html.destroyNode(tn); |
| return nodes; // array |
| } |
| |
| dojo.html.placeOnScreen = function( |
| /* HTMLElement */node, |
| /* integer */desiredX, |
| /* integer */desiredY, |
| /* integer */padding, |
| /* boolean? */hasScroll, |
| /* string? */corners, |
| /* boolean? */tryOnly |
| ){ |
| // summary |
| // Keeps 'node' in the visible area of the screen while trying to |
| // place closest to desiredX, desiredY. The input coordinates are |
| // expected to be the desired screen position, not accounting for |
| // scrolling. If you already accounted for scrolling, set 'hasScroll' |
| // to true. Set padding to either a number or array for [paddingX, paddingY] |
| // to put some buffer around the element you want to position. |
| // Set which corner(s) you want to bind to, such as |
| // |
| // placeOnScreen(node, desiredX, desiredY, padding, hasScroll, "TR") |
| // placeOnScreen(node, [desiredX, desiredY], padding, hasScroll, ["TR", "BL"]) |
| // |
| // The desiredX/desiredY will be treated as the topleft(TL)/topright(TR) or |
| // BottomLeft(BL)/BottomRight(BR) corner of the node. Each corner is tested |
| // and if a perfect match is found, it will be used. Otherwise, it goes through |
| // all of the specified corners, and choose the most appropriate one. |
| // By default, corner = ['TL']. |
| // If tryOnly is set to true, the node will not be moved to the place. |
| // |
| // NOTE: node is assumed to be absolutely or relatively positioned. |
| // |
| // Alternate call sig: |
| // placeOnScreen(node, [x, y], padding, hasScroll) |
| // |
| // Examples: |
| // placeOnScreen(node, 100, 200) |
| // placeOnScreen("myId", [800, 623], 5) |
| // placeOnScreen(node, 234, 3284, [2, 5], true) |
| |
| // TODO: make this function have variable call sigs |
| // kes(node, ptArray, cornerArray, padding, hasScroll) |
| // kes(node, ptX, ptY, cornerA, cornerB, cornerC, paddingArray, hasScroll) |
| if(desiredX instanceof Array || typeof desiredX == "array") { |
| tryOnly = corners; |
| corners = hasScroll; |
| hasScroll = padding; |
| padding = desiredY; |
| desiredY = desiredX[1]; |
| desiredX = desiredX[0]; |
| } |
| |
| if(corners instanceof String || typeof corners == "string"){ |
| corners = corners.split(","); |
| } |
| |
| if(!isNaN(padding)) { |
| padding = [Number(padding), Number(padding)]; |
| } else if(!(padding instanceof Array || typeof padding == "array")) { |
| padding = [0, 0]; |
| } |
| |
| var scroll = dojo.html.getScroll().offset; |
| var view = dojo.html.getViewport(); |
| |
| node = dojo.byId(node); |
| var oldDisplay = node.style.display; |
| node.style.display=""; |
| var bb = dojo.html.getBorderBox(node); |
| var w = bb.width; |
| var h = bb.height; |
| node.style.display=oldDisplay; |
| |
| if(!(corners instanceof Array || typeof corners == "array")){ |
| corners = ['TL']; |
| } |
| |
| var bestx, besty, bestDistance = Infinity, bestCorner; |
| |
| for(var cidex=0; cidex<corners.length; ++cidex){ |
| var corner = corners[cidex]; |
| var match = true; |
| var tryX = desiredX - (corner.charAt(1)=='L' ? 0 : w) + padding[0]*(corner.charAt(1)=='L' ? 1 : -1); |
| var tryY = desiredY - (corner.charAt(0)=='T' ? 0 : h) + padding[1]*(corner.charAt(0)=='T' ? 1 : -1); |
| if(hasScroll) { |
| tryX -= scroll.x; |
| tryY -= scroll.y; |
| } |
| |
| if(tryX < 0){ |
| tryX = 0; |
| match = false; |
| } |
| |
| if(tryY < 0){ |
| tryY = 0; |
| match = false; |
| } |
| |
| var x = tryX + w; |
| if(x > view.width) { |
| x = view.width - w; |
| match = false; |
| } else { |
| x = tryX; |
| } |
| x = Math.max(padding[0], x) + scroll.x; |
| |
| var y = tryY + h; |
| if(y > view.height) { |
| y = view.height - h; |
| match = false; |
| } else { |
| y = tryY; |
| } |
| y = Math.max(padding[1], y) + scroll.y; |
| |
| if(match){ //perfect match, return now |
| bestx = x; |
| besty = y; |
| bestDistance = 0; |
| bestCorner = corner; |
| break; |
| }else{ |
| //not perfect, find out whether it is better than the saved one |
| var dist = Math.pow(x-tryX-scroll.x,2)+Math.pow(y-tryY-scroll.y,2); |
| if(bestDistance > dist){ |
| bestDistance = dist; |
| bestx = x; |
| besty = y; |
| bestCorner = corner; |
| } |
| } |
| } |
| |
| if(!tryOnly){ |
| node.style.left = bestx + "px"; |
| node.style.top = besty + "px"; |
| } |
| |
| return { left: bestx, top: besty, x: bestx, y: besty, dist: bestDistance, corner: bestCorner}; // object |
| } |
| |
| dojo.html.placeOnScreenPoint = function(node, desiredX, desiredY, padding, hasScroll) { |
| dojo.deprecated("dojo.html.placeOnScreenPoint", "use dojo.html.placeOnScreen() instead", "0.5"); |
| return dojo.html.placeOnScreen(node, desiredX, desiredY, padding, hasScroll, ['TL', 'TR', 'BL', 'BR']); |
| } |
| |
| dojo.html.placeOnScreenAroundElement = function( |
| /* HTMLElement */node, |
| /* HTMLElement */aroundNode, |
| /* integer */padding, |
| /* string? */aroundType, |
| /* string? */aroundCorners, |
| /* boolean? */tryOnly |
| ){ |
| // summary |
| // Like placeOnScreen, except it accepts aroundNode instead of x,y |
| // and attempts to place node around it. aroundType (see |
| // dojo.html.boxSizing in html/layout.js) determines which box of the |
| // aroundNode should be used to calculate the outer box. |
| // aroundCorners specify Which corner of aroundNode should be |
| // used to place the node => which corner(s) of node to use (see the |
| // corners parameter in dojo.html.placeOnScreen) |
| // aroundCorners: {'TL': 'BL', 'BL': 'TL'} |
| |
| var best, bestDistance=Infinity; |
| aroundNode = dojo.byId(aroundNode); |
| var oldDisplay = aroundNode.style.display; |
| aroundNode.style.display=""; |
| var mb = dojo.html.getElementBox(aroundNode, aroundType); |
| var aroundNodeW = mb.width; |
| var aroundNodeH = mb.height; |
| var aroundNodePos = dojo.html.getAbsolutePosition(aroundNode, true, aroundType); |
| aroundNode.style.display=oldDisplay; |
| |
| for(var nodeCorner in aroundCorners){ |
| var pos, desiredX, desiredY; |
| var corners = aroundCorners[nodeCorner]; |
| |
| desiredX = aroundNodePos.x + (nodeCorner.charAt(1)=='L' ? 0 : aroundNodeW); |
| desiredY = aroundNodePos.y + (nodeCorner.charAt(0)=='T' ? 0 : aroundNodeH); |
| |
| pos = dojo.html.placeOnScreen(node, desiredX, desiredY, padding, true, corners, true); |
| if(pos.dist == 0){ |
| best = pos; |
| break; |
| }else{ |
| //not perfect, find out whether it is better than the saved one |
| if(bestDistance > pos.dist){ |
| bestDistance = pos.dist; |
| best = pos; |
| } |
| } |
| } |
| |
| if(!tryOnly){ |
| node.style.left = best.left + "px"; |
| node.style.top = best.top + "px"; |
| } |
| return best; // object |
| } |
| |
| dojo.html.scrollIntoView = function(/* HTMLElement */node){ |
| // summary |
| // Scroll the passed node into view, if it is not. |
| if(!node){ return; } |
| |
| // don't rely on that node.scrollIntoView works just because the function is there |
| // it doesnt work in Konqueror or Opera even though the function is there and probably |
| // not safari either |
| // dont like browser sniffs implementations but sometimes you have to use it |
| if(dojo.render.html.ie){ |
| //only call scrollIntoView if there is a scrollbar for this menu, |
| //otherwise, scrollIntoView will scroll the window scrollbar |
| if(dojo.html.getBorderBox(node.parentNode).height <= node.parentNode.scrollHeight){ |
| node.scrollIntoView(false); |
| } |
| }else if(dojo.render.html.mozilla){ |
| // IE, mozilla |
| node.scrollIntoView(false); |
| }else{ |
| var parent = node.parentNode; |
| var parentBottom = parent.scrollTop + dojo.html.getBorderBox(parent).height; |
| var nodeBottom = node.offsetTop + dojo.html.getMarginBox(node).height; |
| if(parentBottom < nodeBottom){ |
| parent.scrollTop += (nodeBottom - parentBottom); |
| }else if(parent.scrollTop > node.offsetTop){ |
| parent.scrollTop -= (parent.scrollTop - node.offsetTop); |
| } |
| } |
| } |
| |
| dojo.provide("dojo.gfx.color"); |
| dojo.require("dojo.lang.common"); |
| dojo.require("dojo.lang.array"); |
| |
| // TODO: rewrite the "x2y" methods to take advantage of the parsing |
| // abilities of the Color object. Also, beef up the Color |
| // object (as possible) to parse most common formats |
| |
| // takes an r, g, b, a(lpha) value, [r, g, b, a] array, "rgb(...)" string, hex string (#aaa, #aaaaaa, aaaaaaa) |
| dojo.gfx.color.Color = function(r, g, b, a) { |
| // dojo.debug("r:", r[0], "g:", r[1], "b:", r[2]); |
| if(dojo.lang.isArray(r)){ |
| this.r = r[0]; |
| this.g = r[1]; |
| this.b = r[2]; |
| this.a = r[3]||1.0; |
| }else if(dojo.lang.isString(r)){ |
| var rgb = dojo.gfx.color.extractRGB(r); |
| this.r = rgb[0]; |
| this.g = rgb[1]; |
| this.b = rgb[2]; |
| this.a = g||1.0; |
| }else if(r instanceof dojo.gfx.color.Color){ |
| // why does this create a new instance if we were passed one? |
| this.r = r.r; |
| this.b = r.b; |
| this.g = r.g; |
| this.a = r.a; |
| }else{ |
| this.r = r; |
| this.g = g; |
| this.b = b; |
| this.a = a; |
| } |
| } |
| |
| dojo.gfx.color.Color.fromArray = function(arr) { |
| return new dojo.gfx.color.Color(arr[0], arr[1], arr[2], arr[3]); |
| } |
| |
| dojo.extend(dojo.gfx.color.Color, { |
| toRgb: function(includeAlpha) { |
| if(includeAlpha) { |
| return this.toRgba(); |
| } else { |
| return [this.r, this.g, this.b]; |
| } |
| }, |
| toRgba: function() { |
| return [this.r, this.g, this.b, this.a]; |
| }, |
| toHex: function() { |
| return dojo.gfx.color.rgb2hex(this.toRgb()); |
| }, |
| toCss: function() { |
| return "rgb(" + this.toRgb().join() + ")"; |
| }, |
| toString: function() { |
| return this.toHex(); // decent default? |
| }, |
| blend: function(color, weight){ |
| var rgb = null; |
| if(dojo.lang.isArray(color)){ |
| rgb = color; |
| }else if(color instanceof dojo.gfx.color.Color){ |
| rgb = color.toRgb(); |
| }else{ |
| rgb = new dojo.gfx.color.Color(color).toRgb(); |
| } |
| return dojo.gfx.color.blend(this.toRgb(), rgb, weight); |
| } |
| }); |
| |
| dojo.gfx.color.named = { |
| white: [255,255,255], |
| black: [0,0,0], |
| red: [255,0,0], |
| green: [0,255,0], |
| lime: [0,255,0], |
| blue: [0,0,255], |
| navy: [0,0,128], |
| gray: [128,128,128], |
| silver: [192,192,192] |
| }; |
| |
| dojo.gfx.color.blend = function(a, b, weight){ |
| // summary: |
| // blend colors a and b (both as RGB array or hex strings) with weight |
| // from -1 to +1, 0 being a 50/50 blend |
| if(typeof a == "string"){ |
| return dojo.gfx.color.blendHex(a, b, weight); |
| } |
| if(!weight){ |
| weight = 0; |
| } |
| weight = Math.min(Math.max(-1, weight), 1); |
| |
| // alex: this interface blows. |
| // map -1 to 1 to the range 0 to 1 |
| weight = ((weight + 1)/2); |
| |
| var c = []; |
| |
| // var stop = (1000*weight); |
| for(var x = 0; x < 3; x++){ |
| c[x] = parseInt( b[x] + ( (a[x] - b[x]) * weight) ); |
| } |
| return c; |
| } |
| |
| // very convenient blend that takes and returns hex values |
| // (will get called automatically by blend when blend gets strings) |
| dojo.gfx.color.blendHex = function(a, b, weight) { |
| return dojo.gfx.color.rgb2hex(dojo.gfx.color.blend(dojo.gfx.color.hex2rgb(a), dojo.gfx.color.hex2rgb(b), weight)); |
| } |
| |
| // get RGB array from css-style color declarations |
| dojo.gfx.color.extractRGB = function(color) { |
| var hex = "0123456789abcdef"; |
| color = color.toLowerCase(); |
| if( color.indexOf("rgb") == 0 ) { |
| var matches = color.match(/rgba*\((\d+), *(\d+), *(\d+)/i); |
| var ret = matches.splice(1, 3); |
| return ret; |
| } else { |
| var colors = dojo.gfx.color.hex2rgb(color); |
| if(colors) { |
| return colors; |
| } else { |
| // named color (how many do we support?) |
| return dojo.gfx.color.named[color] || [255, 255, 255]; |
| } |
| } |
| } |
| |
| dojo.gfx.color.hex2rgb = function(hex) { |
| var hexNum = "0123456789ABCDEF"; |
| var rgb = new Array(3); |
| if( hex.indexOf("#") == 0 ) { hex = hex.substring(1); } |
| hex = hex.toUpperCase(); |
| if(hex.replace(new RegExp("["+hexNum+"]", "g"), "") != "") { |
| return null; |
| } |
| if( hex.length == 3 ) { |
| rgb[0] = hex.charAt(0) + hex.charAt(0) |
| rgb[1] = hex.charAt(1) + hex.charAt(1) |
| rgb[2] = hex.charAt(2) + hex.charAt(2); |
| } else { |
| rgb[0] = hex.substring(0, 2); |
| rgb[1] = hex.substring(2, 4); |
| rgb[2] = hex.substring(4); |
| } |
| for(var i = 0; i < rgb.length; i++) { |
| rgb[i] = hexNum.indexOf(rgb[i].charAt(0)) * 16 + hexNum.indexOf(rgb[i].charAt(1)); |
| } |
| return rgb; |
| } |
| |
| dojo.gfx.color.rgb2hex = function(r, g, b) { |
| if(dojo.lang.isArray(r)) { |
| g = r[1] || 0; |
| b = r[2] || 0; |
| r = r[0] || 0; |
| } |
| var ret = dojo.lang.map([r, g, b], function(x) { |
| x = new Number(x); |
| var s = x.toString(16); |
| while(s.length < 2) { s = "0" + s; } |
| return s; |
| }); |
| ret.unshift("#"); |
| return ret.join(""); |
| } |
| |
| dojo.provide("dojo.lfx.Animation"); |
| |
| dojo.require("dojo.lang.func"); |
| |
| /* |
| Animation package based on Dan Pupius' work: http://pupius.co.uk/js/Toolkit.Drawing.js |
| */ |
| dojo.lfx.Line = function(/*int*/ start, /*int*/ end){ |
| // summary: dojo.lfx.Line is the object used to generate values |
| // from a start value to an end value |
| this.start = start; |
| this.end = end; |
| if(dojo.lang.isArray(start)){ |
| /* start: Array |
| end: Array |
| pId: a */ |
| var diff = []; |
| dojo.lang.forEach(this.start, function(s,i){ |
| diff[i] = this.end[i] - s; |
| }, this); |
| |
| this.getValue = function(/*float*/ n){ |
| var res = []; |
| dojo.lang.forEach(this.start, function(s, i){ |
| res[i] = (diff[i] * n) + s; |
| }, this); |
| return res; // Array |
| } |
| }else{ |
| var diff = end - start; |
| |
| this.getValue = function(/*float*/ n){ |
| // summary: returns the point on the line |
| // n: a floating point number greater than 0 and less than 1 |
| return (diff * n) + this.start; // Decimal |
| } |
| } |
| } |
| |
| if((dojo.render.html.khtml)&&(!dojo.render.html.safari)){ |
| // the cool kids are obviously not using konqueror... |
| // found a very wierd bug in floats constants, 1.5 evals as 1 |
| // seems somebody mixed up ints and floats in 3.5.4 ?? |
| // FIXME: investigate more and post a KDE bug (Fredrik) |
| dojo.lfx.easeDefault = function(/*Decimal?*/ n){ |
| // summary: Returns the point for point n on a sin wave. |
| return (parseFloat("0.5")+((Math.sin( (n+parseFloat("1.5")) * Math.PI))/2)); |
| } |
| }else{ |
| dojo.lfx.easeDefault = function(/*Decimal?*/ n){ |
| return (0.5+((Math.sin( (n+1.5) * Math.PI))/2)); |
| } |
| } |
| |
| dojo.lfx.easeIn = function(/*Decimal?*/ n){ |
| // summary: returns the point on an easing curve |
| // n: a floating point number greater than 0 and less than 1 |
| return Math.pow(n, 3); |
| } |
| |
| dojo.lfx.easeOut = function(/*Decimal?*/ n){ |
| // summary: returns the point on the line |
| // n: a floating point number greater than 0 and less than 1 |
| return ( 1 - Math.pow(1 - n, 3) ); |
| } |
| |
| dojo.lfx.easeInOut = function(/*Decimal?*/ n){ |
| // summary: returns the point on the line |
| // n: a floating point number greater than 0 and less than 1 |
| return ( (3 * Math.pow(n, 2)) - (2 * Math.pow(n, 3)) ); |
| } |
| |
| dojo.lfx.IAnimation = function(){ |
| // summary: dojo.lfx.IAnimation is an interface that implements |
| // commonly used functions of animation objects |
| } |
| dojo.lang.extend(dojo.lfx.IAnimation, { |
| // public properties |
| curve: null, |
| duration: 1000, |
| easing: null, |
| repeatCount: 0, |
| rate: 10, |
| |
| // events |
| handler: null, |
| beforeBegin: null, |
| onBegin: null, |
| onAnimate: null, |
| onEnd: null, |
| onPlay: null, |
| onPause: null, |
| onStop: null, |
| |
| // public methods |
| play: null, |
| pause: null, |
| stop: null, |
| |
| connect: function(/*Event*/ evt, /*Object*/ scope, /*Function*/ newFunc){ |
| // summary: Convenience function. Quickly connect to an event |
| // of this object and save the old functions connected to it. |
| // evt: The name of the event to connect to. |
| // scope: the scope in which to run newFunc. |
| // newFunc: the function to run when evt is fired. |
| if(!newFunc){ |
| /* scope: Function |
| newFunc: null |
| pId: f */ |
| newFunc = scope; |
| scope = this; |
| } |
| newFunc = dojo.lang.hitch(scope, newFunc); |
| var oldFunc = this[evt]||function(){}; |
| this[evt] = function(){ |
| var ret = oldFunc.apply(this, arguments); |
| newFunc.apply(this, arguments); |
| return ret; |
| } |
| return this; // dojo.lfx.IAnimation |
| }, |
| |
| fire: function(/*Event*/ evt, /*Array*/ args){ |
| // summary: Convenience function. Fire event "evt" and pass it |
| // the arguments specified in "args". |
| // evt: The event to fire. |
| // args: The arguments to pass to the event. |
| if(this[evt]){ |
| this[evt].apply(this, (args||[])); |
| } |
| return this; // dojo.lfx.IAnimation |
| }, |
| |
| repeat: function(/*int*/ count){ |
| // summary: Set the repeat count of this object. |
| // count: How many times to repeat the animation. |
| this.repeatCount = count; |
| return this; // dojo.lfx.IAnimation |
| }, |
| |
| // private properties |
| _active: false, |
| _paused: false |
| }); |
| |
| dojo.lfx.Animation = function( /*Object*/ handlers, |
| /*int*/ duration, |
| /*dojo.lfx.Line*/ curve, |
| /*function*/ easing, |
| /*int*/ repeatCount, |
| /*int*/ rate){ |
| // summary |
| // a generic animation object that fires callbacks into it's handlers |
| // object at various states |
| // handlers: { handler: Function?, onstart: Function?, onstop: Function?, onanimate: Function? } |
| dojo.lfx.IAnimation.call(this); |
| if(dojo.lang.isNumber(handlers)||(!handlers && duration.getValue)){ |
| // no handlers argument: |
| rate = repeatCount; |
| repeatCount = easing; |
| easing = curve; |
| curve = duration; |
| duration = handlers; |
| handlers = null; |
| }else if(handlers.getValue||dojo.lang.isArray(handlers)){ |
| // no handlers or duration: |
| rate = easing; |
| repeatCount = curve; |
| easing = duration; |
| curve = handlers; |
| duration = null; |
| handlers = null; |
| } |
| if(dojo.lang.isArray(curve)){ |
| /* curve: Array |
| pId: a */ |
| this.curve = new dojo.lfx.Line(curve[0], curve[1]); |
| }else{ |
| this.curve = curve; |
| } |
| if(duration != null && duration > 0){ this.duration = duration; } |
| if(repeatCount){ this.repeatCount = repeatCount; } |
| if(rate){ this.rate = rate; } |
| if(handlers){ |
| dojo.lang.forEach([ |
| "handler", "beforeBegin", "onBegin", |
| "onEnd", "onPlay", "onStop", "onAnimate" |
| ], function(item){ |
| if(handlers[item]){ |
| this.connect(item, handlers[item]); |
| } |
| }, this); |
| } |
| if(easing && dojo.lang.isFunction(easing)){ |
| this.easing=easing; |
| } |
| } |
| dojo.inherits(dojo.lfx.Animation, dojo.lfx.IAnimation); |
| dojo.lang.extend(dojo.lfx.Animation, { |
| // "private" properties |
| _startTime: null, |
| _endTime: null, |
| _timer: null, |
| _percent: 0, |
| _startRepeatCount: 0, |
| |
| // public methods |
| play: function(/*int?*/ delay, /*bool?*/ 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. |
| if(gotoStart){ |
| clearTimeout(this._timer); |
| this._active = false; |
| this._paused = false; |
| this._percent = 0; |
| }else if(this._active && !this._paused){ |
| return this; // dojo.lfx.Animation |
| } |
| |
| this.fire("handler", ["beforeBegin"]); |
| this.fire("beforeBegin"); |
| |
| if(delay > 0){ |
| setTimeout(dojo.lang.hitch(this, function(){ this.play(null, gotoStart); }), delay); |
| return this; // dojo.lfx.Animation |
| } |
| |
| this._startTime = new Date().valueOf(); |
| if(this._paused){ |
| this._startTime -= (this.duration * this._percent / 100); |
| } |
| this._endTime = this._startTime + this.duration; |
| |
| this._active = true; |
| this._paused = false; |
| |
| var step = this._percent / 100; |
| var value = this.curve.getValue(step); |
| if(this._percent == 0 ){ |
| if(!this._startRepeatCount){ |
| this._startRepeatCount = this.repeatCount; |
| } |
| this.fire("handler", ["begin", value]); |
| this.fire("onBegin", [value]); |
| } |
| |
| this.fire("handler", ["play", value]); |
| this.fire("onPlay", [value]); |
| |
| this._cycle(); |
| return this; // dojo.lfx.Animation |
| }, |
| |
| pause: function(){ |
| // summary: Pauses a running animation. |
| clearTimeout(this._timer); |
| if(!this._active){ return this; /*dojo.lfx.Animation*/} |
| this._paused = true; |
| var value = this.curve.getValue(this._percent / 100); |
| this.fire("handler", ["pause", value]); |
| this.fire("onPause", [value]); |
| return this; // dojo.lfx.Animation |
| }, |
| |
| gotoPercent: function(/*Decimal*/ pct, /*bool?*/ andPlay){ |
| // summary: Sets the progress of the animation. |
| // pct: A percentage in decimal notation (between and including 0.0 and 1.0). |
| // andPlay: If true, play the animation after setting the progress. |
| clearTimeout(this._timer); |
| this._active = true; |
| this._paused = true; |
| this._percent = pct; |
| if(andPlay){ this.play(); } |
| return this; // dojo.lfx.Animation |
| }, |
| |
| stop: function(/*bool?*/ gotoEnd){ |
| // summary: Stops a running animation. |
| // gotoEnd: If true, the animation will end. |
| clearTimeout(this._timer); |
| var step = this._percent / 100; |
| if(gotoEnd){ |
| step = 1; |
| } |
| var value = this.curve.getValue(step); |
| this.fire("handler", ["stop", value]); |
| this.fire("onStop", [value]); |
| this._active = false; |
| this._paused = false; |
| return this; // dojo.lfx.Animation |
| }, |
| |
| status: function(){ |
| // summary: Returns a string representation of the status of |
| // the animation. |
| if(this._active){ |
| return this._paused ? "paused" : "playing"; // String |
| }else{ |
| return "stopped"; // String |
| } |
| return this; |
| }, |
| |
| // "private" methods |
| _cycle: function(){ |
| clearTimeout(this._timer); |
| if(this._active){ |
| var curr = new Date().valueOf(); |
| var step = (curr - this._startTime) / (this._endTime - this._startTime); |
| |
| if(step >= 1){ |
| step = 1; |
| this._percent = 100; |
| }else{ |
| this._percent = step * 100; |
| } |
| |
| // Perform easing |
| if((this.easing)&&(dojo.lang.isFunction(this.easing))){ |
| step = this.easing(step); |
| } |
| |
| var value = this.curve.getValue(step); |
| this.fire("handler", ["animate", value]); |
| this.fire("onAnimate", [value]); |
| |
| if( step < 1 ){ |
| this._timer = setTimeout(dojo.lang.hitch(this, "_cycle"), this.rate); |
| }else{ |
| this._active = false; |
| this.fire("handler", ["end"]); |
| this.fire("onEnd"); |
| |
| if(this.repeatCount > 0){ |
| this.repeatCount--; |
| this.play(null, true); |
| }else if(this.repeatCount == -1){ |
| this.play(null, true); |
| }else{ |
| if(this._startRepeatCount){ |
| this.repeatCount = this._startRepeatCount; |
| this._startRepeatCount = 0; |
| } |
| } |
| } |
| } |
| return this; // dojo.lfx.Animation |
| } |
| }); |
| |
| dojo.lfx.Combine = function(/*dojo.lfx.IAnimation...*/ animations){ |
| // summary: An animation object to play animations passed to it at the same time. |
| dojo.lfx.IAnimation.call(this); |
| this._anims = []; |
| this._animsEnded = 0; |
| |
| var anims = arguments; |
| if(anims.length == 1 && (dojo.lang.isArray(anims[0]) || dojo.lang.isArrayLike(anims[0]))){ |
| /* animations: dojo.lfx.IAnimation[] |
| pId: a */ |
| anims = anims[0]; |
| } |
| |
| dojo.lang.forEach(anims, function(anim){ |
| this._anims.push(anim); |
| anim.connect("onEnd", dojo.lang.hitch(this, "_onAnimsEnded")); |
| }, this); |
| } |
| dojo.inherits(dojo.lfx.Combine, dojo.lfx.IAnimation); |
| dojo.lang.extend(dojo.lfx.Combine, { |
| // private members |
| _animsEnded: 0, |
| |
| // public methods |
| play: function(/*int?*/ delay, /*bool?*/ gotoStart){ |
| // summary: Start the animations. |
| // delay: How many milliseconds to delay before starting. |
| // gotoStart: If true, starts the animations from the beginning; otherwise, |
| // starts them from their current position. |
| if( !this._anims.length ){ return this; /*dojo.lfx.Combine*/} |
| |
| this.fire("beforeBegin"); |
| |
| if(delay > 0){ |
| setTimeout(dojo.lang.hitch(this, function(){ this.play(null, gotoStart); }), delay); |
| return this; // dojo.lfx.Combine |
| } |
| |
| if(gotoStart || this._anims[0].percent == 0){ |
| this.fire("onBegin"); |
| } |
| this.fire("onPlay"); |
| this._animsCall("play", null, gotoStart); |
| return this; // dojo.lfx.Combine |
| }, |
| |
| pause: function(){ |
| // summary: Pauses the running animations. |
| this.fire("onPause"); |
| this._animsCall("pause"); |
| return this; // dojo.lfx.Combine |
| }, |
| |
| stop: function(/*bool?*/ gotoEnd){ |
| // summary: Stops the running animations. |
| // gotoEnd: If true, the animations will end. |
| this.fire("onStop"); |
| this._animsCall("stop", gotoEnd); |
| return this; // dojo.lfx.Combine |
| }, |
| |
| // private methods |
| _onAnimsEnded: function(){ |
| this._animsEnded++; |
| if(this._animsEnded >= this._anims.length){ |
| this.fire("onEnd"); |
| } |
| return this; // dojo.lfx.Combine |
| }, |
| |
| _animsCall: function(/*String*/ funcName){ |
| var args = []; |
| if(arguments.length > 1){ |
| for(var i = 1 ; i < arguments.length ; i++){ |
| args.push(arguments[i]); |
| } |
| } |
| var _this = this; |
| dojo.lang.forEach(this._anims, function(anim){ |
| anim[funcName](args); |
| }, _this); |
| return this; // dojo.lfx.Combine |
| } |
| }); |
| |
| dojo.lfx.Chain = function(/*dojo.lfx.IAnimation...*/ animations) { |
| // summary: An animation object to play animations passed to it |
| // one after another. |
| dojo.lfx.IAnimation.call(this); |
| this._anims = []; |
| this._currAnim = -1; |
| |
| var anims = arguments; |
| if(anims.length == 1 && (dojo.lang.isArray(anims[0]) || dojo.lang.isArrayLike(anims[0]))){ |
| /* animations: dojo.lfx.IAnimation[] |
| pId: a */ |
| anims = anims[0]; |
| } |
| |
| var _this = this; |
| dojo.lang.forEach(anims, function(anim, i, anims_arr){ |
| this._anims.push(anim); |
| if(i < anims_arr.length - 1){ |
| anim.connect("onEnd", dojo.lang.hitch(this, "_playNext") ); |
| }else{ |
| anim.connect("onEnd", dojo.lang.hitch(this, function(){ this.fire("onEnd"); }) ); |
| } |
| }, this); |
| } |
| dojo.inherits(dojo.lfx.Chain, dojo.lfx.IAnimation); |
| dojo.lang.extend(dojo.lfx.Chain, { |
| // private members |
| _currAnim: -1, |
| |
| // public methods |
| play: function(/*int?*/ delay, /*bool?*/ gotoStart){ |
| // summary: Start the animation sequence. |
| // delay: How many milliseconds to delay before starting. |
| // gotoStart: If true, starts the sequence from the beginning; otherwise, |
| // starts it from its current position. |
| if( !this._anims.length ) { return this; /*dojo.lfx.Chain*/} |
| if( gotoStart || !this._anims[this._currAnim] ) { |
| this._currAnim = 0; |
| } |
| |
| var currentAnimation = this._anims[this._currAnim]; |
| |
| this.fire("beforeBegin"); |
| if(delay > 0){ |
| setTimeout(dojo.lang.hitch(this, function(){ this.play(null, gotoStart); }), delay); |
| return this; // dojo.lfx.Chain |
| } |
| |
| if(currentAnimation){ |
| if(this._currAnim == 0){ |
| this.fire("handler", ["begin", this._currAnim]); |
| this.fire("onBegin", [this._currAnim]); |
| } |
| this.fire("onPlay", [this._currAnim]); |
| currentAnimation.play(null, gotoStart); |
| } |
| return this; // dojo.lfx.Chain |
| }, |
| |
| pause: function(){ |
| // summary: Pauses the running animation sequence. |
| if( this._anims[this._currAnim] ) { |
| this._anims[this._currAnim].pause(); |
| this.fire("onPause", [this._currAnim]); |
| } |
| return this; // dojo.lfx.Chain |
| }, |
| |
| playPause: function(){ |
| // summary: If the animation sequence is playing, pause it; otherwise, |
| // play it. |
| if(this._anims.length == 0){ return this; } |
| if(this._currAnim == -1){ this._currAnim = 0; } |
| var currAnim = this._anims[this._currAnim]; |
| if( currAnim ) { |
| if( !currAnim._active || currAnim._paused ) { |
| this.play(); |
| } else { |
| this.pause(); |
| } |
| } |
| return this; // dojo.lfx.Chain |
| }, |
| |
| stop: function(){ |
| // summary: Stops the running animations. |
| var currAnim = this._anims[this._currAnim]; |
| if(currAnim){ |
| currAnim.stop(); |
| this.fire("onStop", [this._currAnim]); |
| } |
| return currAnim; // dojo.lfx.IAnimation |
| }, |
| |
| // private methods |
| _playNext: function(){ |
| if( this._currAnim == -1 || this._anims.length == 0 ) { return this; } |
| this._currAnim++; |
| if( this._anims[this._currAnim] ){ |
| this._anims[this._currAnim].play(null, true); |
| } |
| return this; // dojo.lfx.Chain |
| } |
| }); |
| |
| dojo.lfx.combine = function(/*dojo.lfx.IAnimation...*/ animations){ |
| // summary: Convenience function. Returns a dojo.lfx.Combine created |
| // using the animations passed in. |
| var anims = arguments; |
| if(dojo.lang.isArray(arguments[0])){ |
| /* animations: dojo.lfx.IAnimation[] |
| pId: a */ |
| anims = arguments[0]; |
| } |
| if(anims.length == 1){ return anims[0]; } |
| return new dojo.lfx.Combine(anims); // dojo.lfx.Combine |
| } |
| |
| dojo.lfx.chain = function(/*dojo.lfx.IAnimation...*/ animations){ |
| // summary: Convenience function. Returns a dojo.lfx.Chain created |
| // using the animations passed in. |
| var anims = arguments; |
| if(dojo.lang.isArray(arguments[0])){ |
| /* animations: dojo.lfx.IAnimation[] |
| pId: a */ |
| anims = arguments[0]; |
| } |
| if(anims.length == 1){ return anims[0]; } |
| return new dojo.lfx.Chain(anims); // dojo.lfx.Combine |
| } |
| |
| dojo.require("dojo.html.style"); |
| dojo.provide("dojo.html.color"); |
| |
| |
| dojo.require("dojo.lang.common"); |
| |
| dojo.html.getBackgroundColor = function(/* HTMLElement */node){ |
| // summary |
| // returns the background color of the passed node as a 32-bit color (RGBA) |
| node = dojo.byId(node); |
| var color; |
| do{ |
| color = dojo.html.getStyle(node, "background-color"); |
| // Safari doesn't say "transparent" |
| if(color.toLowerCase() == "rgba(0, 0, 0, 0)") { color = "transparent"; } |
| if(node == document.getElementsByTagName("body")[0]) { node = null; break; } |
| node = node.parentNode; |
| }while(node && dojo.lang.inArray(["transparent", ""], color)); |
| if(color == "transparent"){ |
| color = [255, 255, 255, 0]; |
| }else{ |
| color = dojo.gfx.color.extractRGB(color); |
| } |
| return color; // array |
| } |
| |
| dojo.provide("dojo.lfx.html"); |
| |
| |
| |
| dojo.require("dojo.lang.array"); |
| dojo.require("dojo.html.display"); |
| |
| |
| |
| dojo.lfx.html._byId = function(nodes){ |
| if(!nodes){ return []; } |
| if(dojo.lang.isArrayLike(nodes)){ |
| if(!nodes.alreadyChecked){ |
| var n = []; |
| dojo.lang.forEach(nodes, function(node){ |
| n.push(dojo.byId(node)); |
| }); |
| n.alreadyChecked = true; |
| return n; |
| }else{ |
| return nodes; |
| } |
| }else{ |
| var n = []; |
| n.push(dojo.byId(nodes)); |
| n.alreadyChecked = true; |
| return n; |
| } |
| } |
| |
| dojo.lfx.html.propertyAnimation = function( /*DOMNode[]*/ nodes, |
| /*Object[]*/ propertyMap, |
| /*int*/ duration, |
| /*function*/ easing, |
| /*Object*/ handlers){ |
| // summary: Returns an animation that will transition the properties of "nodes" |
| // depending how they are defined in "propertyMap". |
| // nodes: An array of DOMNodes or one DOMNode. |
| // propertyMap: { property: String, start: Decimal?, end: Decimal?, units: String? } |
| // An array of objects defining properties to change. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // handlers: { handler: Function?, onstart: Function?, onstop: Function?, onanimate: Function? } |
| nodes = dojo.lfx.html._byId(nodes); |
| |
| var targs = { |
| "propertyMap": propertyMap, |
| "nodes": nodes, |
| "duration": duration, |
| "easing": easing||dojo.lfx.easeDefault |
| }; |
| |
| var setEmUp = function(args){ |
| if(args.nodes.length==1){ |
| // FIXME: we're only supporting start-value filling when one node is |
| // passed |
| |
| var pm = args.propertyMap; |
| if(!dojo.lang.isArray(args.propertyMap)){ |
| // it's stupid to have to pack an array with a set of objects |
| // when you can just pass in an object list |
| var parr = []; |
| for(var pname in pm){ |
| pm[pname].property = pname; |
| parr.push(pm[pname]); |
| } |
| pm = args.propertyMap = parr; |
| } |
| dojo.lang.forEach(pm, function(prop){ |
| if(dj_undef("start", prop)){ |
| if(prop.property != "opacity"){ |
| prop.start = parseInt(dojo.html.getComputedStyle(args.nodes[0], prop.property)); |
| }else{ |
| prop.start = dojo.html.getOpacity(args.nodes[0]); |
| } |
| } |
| }); |
| } |
| } |
| |
| var coordsAsInts = function(coords){ |
| var cints = []; |
| dojo.lang.forEach(coords, function(c){ |
| cints.push(Math.round(c)); |
| }); |
| return cints; |
| } |
| |
| var setStyle = function(n, style){ |
| n = dojo.byId(n); |
| if(!n || !n.style){ return; } |
| for(var s in style){ |
| try{ |
| if(s == "opacity"){ |
| dojo.html.setOpacity(n, style[s]); |
| }else{ |
| n.style[s] = style[s]; |
| } |
| }catch(e){ dojo.debug(e); } |
| } |
| } |
| |
| var propLine = function(properties){ |
| this._properties = properties; |
| this.diffs = new Array(properties.length); |
| dojo.lang.forEach(properties, function(prop, i){ |
| // calculate the end - start to optimize a bit |
| if(dojo.lang.isFunction(prop.start)){ |
| prop.start = prop.start(prop, i); |
| } |
| if(dojo.lang.isFunction(prop.end)){ |
| prop.end = prop.end(prop, i); |
| } |
| if(dojo.lang.isArray(prop.start)){ |
| // don't loop through the arrays |
| this.diffs[i] = null; |
| }else if(prop.start instanceof dojo.gfx.color.Color){ |
| // save these so we don't have to call toRgb() every getValue() call |
| prop.startRgb = prop.start.toRgb(); |
| prop.endRgb = prop.end.toRgb(); |
| }else{ |
| this.diffs[i] = prop.end - prop.start; |
| } |
| }, this); |
| |
| this.getValue = function(n){ |
| var ret = {}; |
| dojo.lang.forEach(this._properties, function(prop, i){ |
| var value = null; |
| if(dojo.lang.isArray(prop.start)){ |
| // FIXME: what to do here? |
| }else if(prop.start instanceof dojo.gfx.color.Color){ |
| value = (prop.units||"rgb") + "("; |
| for(var j = 0 ; j < prop.startRgb.length ; j++){ |
| value += Math.round(((prop.endRgb[j] - prop.startRgb[j]) * n) + prop.startRgb[j]) + (j < prop.startRgb.length - 1 ? "," : ""); |
| } |
| value += ")"; |
| }else{ |
| value = ((this.diffs[i]) * n) + prop.start + (prop.property != "opacity" ? prop.units||"px" : ""); |
| } |
| ret[dojo.html.toCamelCase(prop.property)] = value; |
| }, this); |
| return ret; |
| } |
| } |
| |
| var anim = new dojo.lfx.Animation({ |
| beforeBegin: function(){ |
| setEmUp(targs); |
| anim.curve = new propLine(targs.propertyMap); |
| }, |
| onAnimate: function(propValues){ |
| dojo.lang.forEach(targs.nodes, function(node){ |
| setStyle(node, propValues); |
| }); |
| } |
| }, |
| targs.duration, |
| null, |
| targs.easing |
| ); |
| if(handlers){ |
| for(var x in handlers){ |
| if(dojo.lang.isFunction(handlers[x])){ |
| anim.connect(x, anim, handlers[x]); |
| } |
| } |
| } |
| |
| return anim; // dojo.lfx.Animation |
| } |
| |
| dojo.lfx.html._makeFadeable = function(nodes){ |
| var makeFade = function(node){ |
| if(dojo.render.html.ie){ |
| // only set the zoom if the "tickle" value would be the same as the |
| // default |
| if( (node.style.zoom.length == 0) && |
| (dojo.html.getStyle(node, "zoom") == "normal") ){ |
| // make sure the node "hasLayout" |
| // NOTE: this has been tested with larger and smaller user-set text |
| // sizes and works fine |
| node.style.zoom = "1"; |
| // node.style.zoom = "normal"; |
| } |
| // don't set the width to auto if it didn't already cascade that way. |
| // We don't want to f anyones designs |
| if( (node.style.width.length == 0) && |
| (dojo.html.getStyle(node, "width") == "auto") ){ |
| node.style.width = "auto"; |
| } |
| } |
| } |
| if(dojo.lang.isArrayLike(nodes)){ |
| dojo.lang.forEach(nodes, makeFade); |
| }else{ |
| makeFade(nodes); |
| } |
| } |
| |
| dojo.lfx.html.fade = function(/*DOMNode[]*/ nodes, |
| /*Object*/values, |
| /*int?*/ duration, |
| /*Function?*/ easing, |
| /*Function?*/ callback){ |
| // summary:Returns an animation that will fade the "nodes" from the start to end values passed. |
| // nodes: An array of DOMNodes or one DOMNode. |
| // values: { start: Decimal?, end: Decimal? } |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| nodes = dojo.lfx.html._byId(nodes); |
| var props = { property: "opacity" }; |
| if(!dj_undef("start", values)){ |
| props.start = values.start; |
| }else{ |
| props.start = function(){ return dojo.html.getOpacity(nodes[0]); }; |
| } |
| |
| if(!dj_undef("end", values)){ |
| props.end = values.end; |
| }else{ |
| dojo.raise("dojo.lfx.html.fade needs an end value"); |
| } |
| |
| var anim = dojo.lfx.propertyAnimation(nodes, [ props ], duration, easing); |
| anim.connect("beforeBegin", function(){ |
| dojo.lfx.html._makeFadeable(nodes); |
| }); |
| if(callback){ |
| anim.connect("onEnd", function(){ callback(nodes, anim); }); |
| } |
| |
| return anim; // dojo.lfx.Animation |
| } |
| |
| dojo.lfx.html.fadeIn = function(/*DOMNode[]*/ nodes, /*int?*/ duration, /*Function?*/ easing, /*Function?*/ callback){ |
| // summary: Returns an animation that will fade "nodes" from its current opacity to fully opaque. |
| // nodes: An array of DOMNodes or one DOMNode. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| return dojo.lfx.html.fade(nodes, { end: 1 }, duration, easing, callback); // dojo.lfx.Animation |
| } |
| |
| dojo.lfx.html.fadeOut = function(/*DOMNode[]*/ nodes, /*int?*/ duration, /*Function?*/ easing, /*Function?*/ callback){ |
| // summary: Returns an animation that will fade "nodes" from its current opacity to fully transparent. |
| // nodes: An array of DOMNodes or one DOMNode. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| return dojo.lfx.html.fade(nodes, { end: 0 }, duration, easing, callback); // dojo.lfx.Animation |
| } |
| |
| dojo.lfx.html.fadeShow = function(/*DOMNode[]*/ nodes, /*int?*/ duration, /*Function?*/ easing, /*Function?*/ callback){ |
| // summary: Returns an animation that will fade "nodes" from transparent to opaque and shows |
| // "nodes" at the end if it is hidden. |
| // nodes: An array of DOMNodes or one DOMNode. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| nodes=dojo.lfx.html._byId(nodes); |
| dojo.lang.forEach(nodes, function(node){ |
| dojo.html.setOpacity(node, 0.0); |
| }); |
| |
| var anim = dojo.lfx.html.fadeIn(nodes, duration, easing, callback); |
| anim.connect("beforeBegin", function(){ |
| if(dojo.lang.isArrayLike(nodes)){ |
| dojo.lang.forEach(nodes, dojo.html.show); |
| }else{ |
| dojo.html.show(nodes); |
| } |
| }); |
| |
| return anim; // dojo.lfx.Animation |
| } |
| |
| dojo.lfx.html.fadeHide = function(/*DOMNode[]*/ nodes, /*int?*/ duration, /*Function?*/ easing, /*Function?*/ callback){ |
| // summary: Returns an animation that will fade "nodes" from its current opacity to opaque and hides |
| // "nodes" at the end. |
| // nodes: An array of DOMNodes or one DOMNode. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| var anim = dojo.lfx.html.fadeOut(nodes, duration, easing, function(){ |
| if(dojo.lang.isArrayLike(nodes)){ |
| dojo.lang.forEach(nodes, dojo.html.hide); |
| }else{ |
| dojo.html.hide(nodes); |
| } |
| if(callback){ callback(nodes, anim); } |
| }); |
| |
| return anim; // dojo.lfx.Animation |
| } |
| |
| dojo.lfx.html.wipeIn = function(/*DOMNode[]*/ nodes, /*int?*/ duration, /*Function?*/ easing, /*Function?*/ callback){ |
| // summary: Returns an animation that will show and wipe in "nodes". |
| // nodes: An array of DOMNodes or one DOMNode. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| nodes = dojo.lfx.html._byId(nodes); |
| var anims = []; |
| |
| dojo.lang.forEach(nodes, function(node){ |
| var oprop = { }; // old properties of node (before we mucked w/them) |
| |
| // get node height, either it's natural height or it's height specified via style or class attributes |
| // (for FF, the node has to be (temporarily) rendered to measure height) |
| // TODO: should this offscreen code be part of dojo.html, so that getBorderBox() works on hidden nodes? |
| var origTop, origLeft, origPosition; |
| with(node.style){ |
| origTop=top; origLeft=left; origPosition=position; |
| top="-9999px"; left="-9999px"; position="absolute"; |
| display=""; |
| } |
| var nodeHeight = dojo.html.getBorderBox(node).height; |
| with(node.style){ |
| top=origTop; left=origLeft; position=origPosition; |
| display="none"; |
| } |
| |
| var anim = dojo.lfx.propertyAnimation(node, |
| { "height": { |
| start: 1, // 0 causes IE to display the whole panel |
| end: function(){ return nodeHeight; } |
| } |
| }, |
| duration, |
| easing); |
| |
| anim.connect("beforeBegin", function(){ |
| oprop.overflow = node.style.overflow; |
| oprop.height = node.style.height; |
| with(node.style){ |
| overflow = "hidden"; |
| height = "1px"; // 0 causes IE to display the whole panel |
| } |
| dojo.html.show(node); |
| }); |
| |
| anim.connect("onEnd", function(){ |
| with(node.style){ |
| overflow = oprop.overflow; |
| height = oprop.height; |
| } |
| if(callback){ callback(node, anim); } |
| }); |
| anims.push(anim); |
| }); |
| |
| return dojo.lfx.combine(anims); // dojo.lfx.Combine |
| } |
| |
| dojo.lfx.html.wipeOut = function(/*DOMNode[]*/ nodes, /*int?*/ duration, /*Function?*/ easing, /*Function?*/ callback){ |
| // summary: Returns an animation that will wipe out and hide "nodes". |
| // nodes: An array of DOMNodes or one DOMNode. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| nodes = dojo.lfx.html._byId(nodes); |
| var anims = []; |
| |
| dojo.lang.forEach(nodes, function(node){ |
| var oprop = { }; // old properties of node (before we mucked w/them) |
| var anim = dojo.lfx.propertyAnimation(node, |
| { "height": { |
| start: function(){ return dojo.html.getContentBox(node).height; }, |
| end: 1 // 0 causes IE to display the whole panel |
| } |
| }, |
| duration, |
| easing, |
| { |
| "beforeBegin": function(){ |
| oprop.overflow = node.style.overflow; |
| oprop.height = node.style.height; |
| with(node.style){ |
| overflow = "hidden"; |
| } |
| dojo.html.show(node); |
| }, |
| |
| "onEnd": function(){ |
| dojo.html.hide(node); |
| with(node.style){ |
| overflow = oprop.overflow; |
| height = oprop.height; |
| } |
| if(callback){ callback(node, anim); } |
| } |
| } |
| ); |
| anims.push(anim); |
| }); |
| |
| return dojo.lfx.combine(anims); // dojo.lfx.Combine |
| } |
| |
| dojo.lfx.html.slideTo = function(/*DOMNode*/ nodes, |
| /*Object*/ coords, |
| /*int?*/ duration, |
| /*Function?*/ easing, |
| /*Function?*/ callback){ |
| // summary: Returns an animation that will slide "nodes" from its current position to |
| // the position defined in "coords". |
| // nodes: An array of DOMNodes or one DOMNode. |
| // coords: { top: Decimal?, left: Decimal? } |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| nodes = dojo.lfx.html._byId(nodes); |
| var anims = []; |
| var compute = dojo.html.getComputedStyle; |
| |
| if(dojo.lang.isArray(coords)){ |
| /* coords: Array |
| pId: a */ |
| dojo.deprecated('dojo.lfx.html.slideTo(node, array)', 'use dojo.lfx.html.slideTo(node, {top: value, left: value});', '0.5'); |
| coords = { top: coords[0], left: coords[1] }; |
| } |
| dojo.lang.forEach(nodes, function(node){ |
| var top = null; |
| var left = null; |
| |
| var init = (function(){ |
| var innerNode = node; |
| return function(){ |
| var pos = compute(innerNode, 'position'); |
| top = (pos == 'absolute' ? node.offsetTop : parseInt(compute(node, 'top')) || 0); |
| left = (pos == 'absolute' ? node.offsetLeft : parseInt(compute(node, 'left')) || 0); |
| |
| if (!dojo.lang.inArray(['absolute', 'relative'], pos)) { |
| var ret = dojo.html.abs(innerNode, true); |
| dojo.html.setStyleAttributes(innerNode, "position:absolute;top:"+ret.y+"px;left:"+ret.x+"px;"); |
| top = ret.y; |
| left = ret.x; |
| } |
| } |
| })(); |
| init(); |
| |
| var anim = dojo.lfx.propertyAnimation(node, |
| { "top": { start: top, end: (coords.top||0) }, |
| "left": { start: left, end: (coords.left||0) } |
| }, |
| duration, |
| easing, |
| { "beforeBegin": init } |
| ); |
| |
| if(callback){ |
| anim.connect("onEnd", function(){ callback(nodes, anim); }); |
| } |
| |
| anims.push(anim); |
| }); |
| |
| return dojo.lfx.combine(anims); // dojo.lfx.Combine |
| } |
| |
| dojo.lfx.html.slideBy = function(/*DOMNode*/ nodes, /*Object*/ coords, /*int?*/ duration, /*Function?*/ easing, /*Function?*/ callback){ |
| // summary: Returns an animation that will slide "nodes" from its current position |
| // to its current position plus the numbers defined in "coords". |
| // nodes: An array of DOMNodes or one DOMNode. |
| // coords: { top: Decimal?, left: Decimal? } |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| nodes = dojo.lfx.html._byId(nodes); |
| var anims = []; |
| var compute = dojo.html.getComputedStyle; |
| |
| if(dojo.lang.isArray(coords)){ |
| /* coords: Array |
| pId: a */ |
| dojo.deprecated('dojo.lfx.html.slideBy(node, array)', 'use dojo.lfx.html.slideBy(node, {top: value, left: value});', '0.5'); |
| coords = { top: coords[0], left: coords[1] }; |
| } |
| |
| dojo.lang.forEach(nodes, function(node){ |
| var top = null; |
| var left = null; |
| |
| var init = (function(){ |
| var innerNode = node; |
| return function(){ |
| var pos = compute(innerNode, 'position'); |
| top = (pos == 'absolute' ? node.offsetTop : parseInt(compute(node, 'top')) || 0); |
| left = (pos == 'absolute' ? node.offsetLeft : parseInt(compute(node, 'left')) || 0); |
| |
| if (!dojo.lang.inArray(['absolute', 'relative'], pos)) { |
| var ret = dojo.html.abs(innerNode, true); |
| dojo.html.setStyleAttributes(innerNode, "position:absolute;top:"+ret.y+"px;left:"+ret.x+"px;"); |
| top = ret.y; |
| left = ret.x; |
| } |
| } |
| })(); |
| init(); |
| |
| var anim = dojo.lfx.propertyAnimation(node, |
| { |
| "top": { start: top, end: top+(coords.top||0) }, |
| "left": { start: left, end: left+(coords.left||0) } |
| }, |
| duration, |
| easing).connect("beforeBegin", init); |
| |
| if(callback){ |
| anim.connect("onEnd", function(){ callback(nodes, anim); }); |
| } |
| |
| anims.push(anim); |
| }); |
| |
| return dojo.lfx.combine(anims); // dojo.lfx.Combine |
| } |
| |
| dojo.lfx.html.explode = function(/*DOMNode*/ start, |
| /*DOMNode*/ endNode, |
| /*int?*/ duration, |
| /*Function?*/ easing, |
| /*Function?*/ callback){ |
| // summary: Returns an animation that will |
| // start: |
| // endNode: |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| var h = dojo.html; |
| start = dojo.byId(start); |
| endNode = dojo.byId(endNode); |
| var startCoords = h.toCoordinateObject(start, true); |
| var outline = document.createElement("div"); |
| h.copyStyle(outline, endNode); |
| if(endNode.explodeClassName){ outline.className = endNode.explodeClassName; } |
| with(outline.style){ |
| position = "absolute"; |
| display = "none"; |
| // border = "1px solid black"; |
| var backgroundStyle = h.getStyle(start, "background-color"); |
| backgroundColor = backgroundStyle ? backgroundStyle.toLowerCase() : "transparent"; |
| backgroundColor = (backgroundColor == "transparent") ? "rgb(221, 221, 221)" : backgroundColor; |
| } |
| dojo.body().appendChild(outline); |
| |
| with(endNode.style){ |
| visibility = "hidden"; |
| display = "block"; |
| } |
| var endCoords = h.toCoordinateObject(endNode, true); |
| with(endNode.style){ |
| display = "none"; |
| visibility = "visible"; |
| } |
| |
| var props = { opacity: { start: 0.5, end: 1.0 } }; |
| dojo.lang.forEach(["height", "width", "top", "left"], function(type){ |
| props[type] = { start: startCoords[type], end: endCoords[type] } |
| }); |
| |
| var anim = new dojo.lfx.propertyAnimation(outline, |
| props, |
| duration, |
| easing, |
| { |
| "beforeBegin": function(){ |
| h.setDisplay(outline, "block"); |
| }, |
| "onEnd": function(){ |
| h.setDisplay(endNode, "block"); |
| outline.parentNode.removeChild(outline); |
| } |
| } |
| ); |
| |
| if(callback){ |
| anim.connect("onEnd", function(){ callback(endNode, anim); }); |
| } |
| return anim; // dojo.lfx.Animation |
| } |
| |
| dojo.lfx.html.implode = function(/*DOMNode*/ startNode, |
| /*DOMNode*/ end, |
| /*int?*/ duration, |
| /*Function?*/ easing, |
| /*Function?*/ callback){ |
| // summary: Returns an animation that will |
| // startNode: |
| // end: |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| var h = dojo.html; |
| startNode = dojo.byId(startNode); |
| end = dojo.byId(end); |
| var startCoords = dojo.html.toCoordinateObject(startNode, true); |
| var endCoords = dojo.html.toCoordinateObject(end, true); |
| |
| var outline = document.createElement("div"); |
| dojo.html.copyStyle(outline, startNode); |
| if (startNode.explodeClassName) { outline.className = startNode.explodeClassName; } |
| dojo.html.setOpacity(outline, 0.3); |
| with(outline.style){ |
| position = "absolute"; |
| display = "none"; |
| backgroundColor = h.getStyle(startNode, "background-color").toLowerCase(); |
| } |
| dojo.body().appendChild(outline); |
| |
| var props = { opacity: { start: 1.0, end: 0.5 } }; |
| dojo.lang.forEach(["height", "width", "top", "left"], function(type){ |
| props[type] = { start: startCoords[type], end: endCoords[type] } |
| }); |
| |
| var anim = new dojo.lfx.propertyAnimation(outline, |
| props, |
| duration, |
| easing, |
| { |
| "beforeBegin": function(){ |
| dojo.html.hide(startNode); |
| dojo.html.show(outline); |
| }, |
| "onEnd": function(){ |
| outline.parentNode.removeChild(outline); |
| } |
| } |
| ); |
| |
| if(callback){ |
| anim.connect("onEnd", function(){ callback(startNode, anim); }); |
| } |
| return anim; // dojo.lfx.Animation |
| } |
| |
| dojo.lfx.html.highlight = function(/*DOMNode[]*/ nodes, |
| /*dojo.gfx.color.Color*/ startColor, |
| /*int?*/ duration, |
| /*Function?*/ easing, |
| /*Function?*/ callback){ |
| // summary: Returns an animation that will set the background color |
| // of "nodes" to startColor and transition it to "nodes" |
| // original color. |
| // startColor: Color to transition from. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| nodes = dojo.lfx.html._byId(nodes); |
| var anims = []; |
| |
| dojo.lang.forEach(nodes, function(node){ |
| var color = dojo.html.getBackgroundColor(node); |
| var bg = dojo.html.getStyle(node, "background-color").toLowerCase(); |
| var bgImage = dojo.html.getStyle(node, "background-image"); |
| var wasTransparent = (bg == "transparent" || bg == "rgba(0, 0, 0, 0)"); |
| while(color.length > 3) { color.pop(); } |
| |
| var rgb = new dojo.gfx.color.Color(startColor); |
| var endRgb = new dojo.gfx.color.Color(color); |
| |
| var anim = dojo.lfx.propertyAnimation(node, |
| { "background-color": { start: rgb, end: endRgb } }, |
| duration, |
| easing, |
| { |
| "beforeBegin": function(){ |
| if(bgImage){ |
| node.style.backgroundImage = "none"; |
| } |
| node.style.backgroundColor = "rgb(" + rgb.toRgb().join(",") + ")"; |
| }, |
| "onEnd": function(){ |
| if(bgImage){ |
| node.style.backgroundImage = bgImage; |
| } |
| if(wasTransparent){ |
| node.style.backgroundColor = "transparent"; |
| } |
| if(callback){ |
| callback(node, anim); |
| } |
| } |
| } |
| ); |
| |
| anims.push(anim); |
| }); |
| return dojo.lfx.combine(anims); // dojo.lfx.Combine |
| } |
| |
| dojo.lfx.html.unhighlight = function(/*DOMNode[]*/ nodes, |
| /*dojo.gfx.color.Color*/ endColor, |
| /*int?*/ duration, |
| /*Function?*/ easing, |
| /*Function?*/ callback){ |
| // summary: Returns an animation that will transition "nodes" background color |
| // from its current color to "endColor". |
| // endColor: Color to transition to. |
| // duration: Duration of the animation in milliseconds. |
| // easing: An easing function. |
| // callback: Function to run at the end of the animation. |
| nodes = dojo.lfx.html._byId(nodes); |
| var anims = []; |
| |
| dojo.lang.forEach(nodes, function(node){ |
| var color = new dojo.gfx.color.Color(dojo.html.getBackgroundColor(node)); |
| var rgb = new dojo.gfx.color.Color(endColor); |
| |
| var bgImage = dojo.html.getStyle(node, "background-image"); |
| |
| var anim = dojo.lfx.propertyAnimation(node, |
| { "background-color": { start: color, end: rgb } }, |
| duration, |
| easing, |
| { |
| "beforeBegin": function(){ |
| if(bgImage){ |
| node.style.backgroundImage = "none"; |
| } |
| node.style.backgroundColor = "rgb(" + color.toRgb().join(",") + ")"; |
| }, |
| "onEnd": function(){ |
| if(callback){ |
| callback(node, anim); |
| } |
| } |
| } |
| ); |
| anims.push(anim); |
| }); |
| return dojo.lfx.combine(anims); // dojo.lfx.Combine |
| } |
| |
| dojo.lang.mixin(dojo.lfx, dojo.lfx.html); |
| |
| dojo.kwCompoundRequire({ |
| browser: ["dojo.lfx.html"], |
| dashboard: ["dojo.lfx.html"] |
| }); |
| dojo.provide("dojo.lfx.*"); |
| |
| dojo.provide("dojo.lfx.toggle"); |
| |
| |
| dojo.lfx.toggle.plain = { |
| show: function(node, duration, easing, callback){ |
| dojo.html.show(node); |
| if(dojo.lang.isFunction(callback)){ callback(); } |
| }, |
| |
| hide: function(node, duration, easing, callback){ |
| dojo.html.hide(node); |
| if(dojo.lang.isFunction(callback)){ callback(); } |
| } |
| } |
| |
| dojo.lfx.toggle.fade = { |
| show: function(node, duration, easing, callback){ |
| dojo.lfx.fadeShow(node, duration, easing, callback).play(); |
| }, |
| |
| hide: function(node, duration, easing, callback){ |
| dojo.lfx.fadeHide(node, duration, easing, callback).play(); |
| } |
| } |
| |
| dojo.lfx.toggle.wipe = { |
| show: function(node, duration, easing, callback){ |
| dojo.lfx.wipeIn(node, duration, easing, callback).play(); |
| }, |
| |
| hide: function(node, duration, easing, callback){ |
| dojo.lfx.wipeOut(node, duration, easing, callback).play(); |
| } |
| } |
| |
| dojo.lfx.toggle.explode = { |
| show: function(node, duration, easing, callback, explodeSrc){ |
| dojo.lfx.explode(explodeSrc||{x:0,y:0,width:0,height:0}, node, duration, easing, callback).play(); |
| }, |
| |
| hide: function(node, duration, easing, callback, explodeSrc){ |
| dojo.lfx.implode(node, explodeSrc||{x:0,y:0,width:0,height:0}, duration, easing, callback).play(); |
| } |
| } |
| |
| dojo.provide("dojo.widget.HtmlWidget"); |
| |
| |
| dojo.require("dojo.html.display"); |
| |
| dojo.require("dojo.lang.extras"); |
| dojo.require("dojo.lang.func"); |
| |
| |
| dojo.declare("dojo.widget.HtmlWidget", dojo.widget.DomWidget, { |
| // summary |
| // Base class for all browser based widgets, or at least "html" widgets. |
| // The meaning of "html" has become unclear; in practice, all widgets derive from this class. |
| |
| // templateCssPath: String |
| // Path to CSS file for this widget |
| templateCssPath: null, |
| |
| // templatePath: String |
| // Path to template (HTML file) for this widget |
| templatePath: null, |
| |
| // lang: String |
| // Language to display this widget in (like en-us). |
| // Defaults to brower's specified preferred language (typically the language of the OS) |
| lang: "", |
| |
| // toggle: String |
| // Controls animation effect for when show() and hide() (or toggle()) are called. |
| // Possible values: "plain", "wipe", "fade", "explode" |
| toggle: "plain", |
| |
| // toggleDuration: Integer |
| // Number of milliseconds for toggle animation effect to complete |
| toggleDuration: 150, |
| |
| initialize: function(args, frag){ |
| // summary: called after the widget is rendered; most subclasses won't override or call this function |
| }, |
| |
| postMixInProperties: function(args, frag){ |
| if(this.lang === ""){this.lang = null;} |
| // now that we know the setting for toggle, get toggle object |
| // (default to plain toggler if user specified toggler not present) |
| this.toggleObj = |
| dojo.lfx.toggle[this.toggle.toLowerCase()] || dojo.lfx.toggle.plain; |
| }, |
| |
| createNodesFromText: function(txt, wrap){ |
| return dojo.html.createNodesFromText(txt, wrap); |
| }, |
| |
| destroyRendering: function(finalize){ |
| try{ |
| if(this.bgIframe){ |
| this.bgIframe.remove(); |
| delete this.bgIframe; |
| } |
| if(!finalize && this.domNode){ |
| dojo.event.browser.clean(this.domNode); |
| } |
| dojo.widget.HtmlWidget.superclass.destroyRendering.call(this); |
| }catch(e){ /* squelch! */ } |
| }, |
| |
| ///////////////////////////////////////////////////////// |
| // Displaying/hiding the widget |
| ///////////////////////////////////////////////////////// |
| isShowing: function(){ |
| // summary |
| // Tests whether widget is set to show-mode or hide-mode (see show() and |
| // hide() methods) |
| // |
| // This function is poorly named. Even if widget is in show-mode, |
| // if it's inside a container that's hidden |
| // (either a container widget, or just a domnode with display:none), |
| // then it won't be displayed |
| return dojo.html.isShowing(this.domNode); // Boolean |
| }, |
| |
| toggleShowing: function(){ |
| // summary: show or hide the widget, to switch it's state |
| if(this.isShowing()){ |
| this.hide(); |
| }else{ |
| this.show(); |
| } |
| }, |
| |
| show: function(){ |
| // summary: show the widget |
| if(this.isShowing()){ return; } |
| this.animationInProgress=true; |
| this.toggleObj.show(this.domNode, this.toggleDuration, null, |
| dojo.lang.hitch(this, this.onShow), this.explodeSrc); |
| }, |
| |
| onShow: function(){ |
| // summary: called after the show() animation has completed |
| this.animationInProgress=false; |
| this.checkSize(); |
| }, |
| |
| hide: function(){ |
| // summary: hide the widget (ending up with display:none) |
| if(!this.isShowing()){ return; } |
| this.animationInProgress = true; |
| this.toggleObj.hide(this.domNode, this.toggleDuration, null, |
| dojo.lang.hitch(this, this.onHide), this.explodeSrc); |
| }, |
| |
| onHide: function(){ |
| // summary: called after the hide() animation has completed |
| this.animationInProgress=false; |
| }, |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Sizing related methods |
| // If the parent changes size then for each child it should call either |
| // - resizeTo(): size the child explicitly |
| // - or checkSize(): notify the child the the parent has changed size |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| _isResized: function(w, h){ |
| // summary |
| // Test if my size has changed. |
| // If width & height are specified then that's my new size; otherwise, |
| // query outerWidth/outerHeight of my domNode |
| |
| // If I'm not being displayed then disregard (show() must |
| // check if the size has changed) |
| if(!this.isShowing()){ return false; } |
| |
| // If my parent has been resized and I have style="height: 100%" |
| // or something similar then my size has changed too. |
| var wh = dojo.html.getMarginBox(this.domNode); |
| var width=w||wh.width; |
| var height=h||wh.height; |
| if(this.width == width && this.height == height){ return false; } |
| |
| this.width=width; |
| this.height=height; |
| return true; |
| }, |
| |
| checkSize: function(){ |
| // summary |
| // Called when my parent has changed size, but my parent won't call resizeTo(). |
| // This is useful if my size is height:100% or something similar. |
| // Also called whenever I am shown, because the first time I am shown I may need |
| // to do size calculations. |
| if(!this._isResized()){ return; } |
| this.onResized(); |
| }, |
| |
| resizeTo: function(w, h){ |
| // summary: explicitly set this widget's size (in pixels). |
| dojo.html.setMarginBox(this.domNode, { width: w, height: h }); |
| |
| // can't do sizing if widget is hidden because referencing node.offsetWidth/node.offsetHeight returns 0. |
| // do sizing on show() instead. |
| if(this.isShowing()){ |
| this.onResized(); |
| } |
| }, |
| |
| resizeSoon: function(){ |
| // summary |
| // schedule onResized() to be called soon, after browser has had |
| // a little more time to calculate the sizes |
| if(this.isShowing()){ |
| dojo.lang.setTimeout(this, this.onResized, 0); |
| } |
| }, |
| |
| onResized: function(){ |
| // summary |
| // Called when my size has changed. |
| // Must notify children if their size has (possibly) changed. |
| dojo.lang.forEach(this.children, function(child){ if(child.checkSize){child.checkSize();} }); |
| } |
| }); |
| |
| dojo.kwCompoundRequire({ |
| common: ["dojo.xml.Parse", |
| "dojo.widget.Widget", |
| "dojo.widget.Parse", |
| "dojo.widget.Manager"], |
| browser: ["dojo.widget.DomWidget", |
| "dojo.widget.HtmlWidget"], |
| dashboard: ["dojo.widget.DomWidget", |
| "dojo.widget.HtmlWidget"], |
| svg: ["dojo.widget.SvgWidget"], |
| rhino: ["dojo.widget.SwtWidget"] |
| }); |
| dojo.provide("dojo.widget.*"); |
| |
| dojo.kwCompoundRequire({ |
| common: ["dojo.io.common"], |
| rhino: ["dojo.io.RhinoIO"], |
| browser: ["dojo.io.BrowserIO", "dojo.io.cookie"], |
| dashboard: ["dojo.io.BrowserIO", "dojo.io.cookie"] |
| }); |
| dojo.provide("dojo.io.*"); |
| |
| dojo.provide("dojo.widget.ContentPane"); |
| |
| |
| |
| |
| dojo.require("dojo.string"); |
| dojo.require("dojo.string.extras"); |
| dojo.require("dojo.html.style"); |
| |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.ContentPane", |
| dojo.widget.HtmlWidget, |
| function(){ |
| // summary: |
| // A widget that can be used as a standalone widget |
| // or as a baseclass for other widgets |
| // Handles replacement of document fragment using either external uri or javascript/java |
| // generated markup or DomNode content, instanciating widgets within content and runs scripts. |
| // Dont confuse it with an iframe, it only needs 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. |
| // scriptScope: Function |
| // reference holder to the inline scripts container, if scriptSeparation is true |
| // bindArgs: String[] |
| // Send in extra args to the dojo.io.bind call |
| |
| // per widgetImpl variables |
| this._styleNodes = []; |
| this._onLoadStack = []; |
| this._onUnloadStack = []; |
| this._callOnUnload = false; |
| this._ioBindObj; |
| // Note: |
| // dont change this value externally |
| this.scriptScope; // undefined for now |
| |
| // loading option |
| // example: |
| // bindArgs="preventCache:false;" overrides cacheContent |
| this.bindArgs = {}; |
| |
| |
| }, { |
| isContainer: true, |
| |
| // loading options |
| // adjustPaths: Boolean |
| // adjust relative paths in markup to fit this page |
| adjustPaths: true, |
| |
| // href: String |
| // The href of the content that displays now |
| // Set this at construction if you want to load externally, |
| // changing href after creation doesnt have any effect, see setUrl |
| href: "", |
| |
| // extractContent Boolean: Extract visible content from inside of <body> .... </body> |
| extractContent: true, |
| |
| // parseContent Boolean: Construct all widgets that is in content |
| parseContent: true, |
| |
| // cacheContent Boolean: Cache content retreived externally |
| cacheContent: true, |
| |
| // preload: Boolean |
| // Force load of data even if pane is hidden. |
| // Note: |
| // In order to delay download you need to initially hide the node it constructs from |
| preload: false, |
| |
| // refreshOnShow: Boolean |
| // Refresh (re-download) content when pane goes from hidden to shown |
| refreshOnShow: false, |
| |
| // handler: String||Function |
| // Generate pane content from a java function |
| // The name of the java proxy function |
| handler: "", |
| |
| // executeScripts: Boolean |
| // Run scripts within content, extractContent has NO effect on this. |
| // Note: |
| // if true scripts in content will be evaled after content is innerHTML'ed |
| executeScripts: false, |
| |
| // scriptSeparation: Boolean |
| // Run scripts in a separate scope, unique for each ContentPane |
| scriptSeparation: true, |
| |
| // loadingMessage: String |
| // Message that shows while downloading |
| loadingMessage: "Loading...", |
| |
| // isLoaded: Boolean |
| // Tells loading status |
| isLoaded: false, |
| |
| postCreate: function(args, frag, parentComp){ |
| if (this.handler!==""){ |
| this.setHandler(this.handler); |
| } |
| if(this.isShowing() || this.preload){ |
| this.loadContents(); |
| } |
| }, |
| |
| show: function(){ |
| // if refreshOnShow is true, reload the contents every time; otherwise, load only the first time |
| if(this.refreshOnShow){ |
| this.refresh(); |
| }else{ |
| this.loadContents(); |
| } |
| dojo.widget.ContentPane.superclass.show.call(this); |
| }, |
| |
| refresh: function(){ |
| // summary: |
| // Force a refresh (re-download) of content, be sure to turn of cache |
| this.isLoaded=false; |
| this.loadContents(); |
| }, |
| |
| loadContents: function() { |
| // summary: |
| // Download if isLoaded is false, else ignore |
| if ( this.isLoaded ){ |
| return; |
| } |
| if ( dojo.lang.isFunction(this.handler)) { |
| this._runHandler(); |
| } else if ( this.href != "" ) { |
| this._downloadExternalContent(this.href, this.cacheContent && !this.refreshOnShow); |
| } |
| }, |
| |
| setUrl: function(/*String||dojo.uri.Uri*/ url) { |
| // summary: |
| // 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 |
| this.href = url; |
| this.isLoaded = false; |
| if ( this.preload || this.isShowing() ){ |
| this.loadContents(); |
| } |
| }, |
| |
| abort: function(){ |
| // summary |
| // Aborts a inflight download of content |
| var bind = this._ioBindObj; |
| if(!bind || !bind.abort){ return; } |
| bind.abort(); |
| delete this._ioBindObj; |
| }, |
| |
| _downloadExternalContent: function(url, useCache) { |
| this.abort(); |
| this._handleDefaults(this.loadingMessage, "onDownloadStart"); |
| var self = this; |
| this._ioBindObj = dojo.io.bind( |
| this._cacheSetting({ |
| url: url, |
| mimetype: "text/html", |
| handler: function(type, data, xhr){ |
| delete self._ioBindObj; // makes sure abort doesnt clear cache |
| if(type=="load"){ |
| self.onDownloadEnd.call(self, url, data); |
| }else{ |
| // XHR isnt a normal JS object, IE doesnt have prototype on XHR so we cant extend it or shallowCopy it |
| var e = { |
| responseText: xhr.responseText, |
| status: xhr.status, |
| statusText: xhr.statusText, |
| responseHeaders: xhr.getAllResponseHeaders(), |
| text: "Error loading '" + url + "' (" + xhr.status + " "+ xhr.statusText + ")" |
| }; |
| self._handleDefaults.call(self, e, "onDownloadError"); |
| self.onLoad(); |
| } |
| } |
| }, useCache) |
| ); |
| }, |
| |
| _cacheSetting: function(bindObj, useCache){ |
| for(var x in this.bindArgs){ |
| if(dojo.lang.isUndefined(bindObj[x])){ |
| bindObj[x] = this.bindArgs[x]; |
| } |
| } |
| |
| if(dojo.lang.isUndefined(bindObj.useCache)){ bindObj.useCache = useCache; } |
| if(dojo.lang.isUndefined(bindObj.preventCache)){ bindObj.preventCache = !useCache; } |
| if(dojo.lang.isUndefined(bindObj.mimetype)){ bindObj.mimetype = "text/html"; } |
| return bindObj; |
| }, |
| |
| onLoad: function(e){ |
| // summary: |
| // Event hook, is called after everything is loaded and widgetified |
| this._runStack("_onLoadStack"); |
| this.isLoaded=true; |
| }, |
| |
| onUnLoad: function(e){ |
| // summary: |
| // Deprecated, use onUnload (lowercased load) |
| dojo.deprecated(this.widgetType+".onUnLoad, use .onUnload (lowercased load)", 0.5); |
| }, |
| |
| onUnload: function(e){ |
| // summary: |
| // Event hook, is called before old content is cleared |
| this._runStack("_onUnloadStack"); |
| delete this.scriptScope; |
| // FIXME: remove for 0.5 along with onUnLoad |
| if(this.onUnLoad !== dojo.widget.ContentPane.prototype.onUnLoad){ |
| this.onUnLoad.apply(this, arguments); |
| } |
| }, |
| |
| _runStack: function(stName){ |
| var st = this[stName]; var err = ""; |
| var scope = this.scriptScope || window; |
| for(var i = 0;i < st.length; i++){ |
| try{ |
| st[i].call(scope); |
| }catch(e){ |
| err += "\n"+st[i]+" failed: "+e.description; |
| } |
| } |
| this[stName] = []; |
| |
| if(err.length){ |
| var name = (stName== "_onLoadStack") ? "addOnLoad" : "addOnUnLoad"; |
| this._handleDefaults(name+" failure\n "+err, "onExecError", "debug"); |
| } |
| }, |
| |
| addOnLoad: function(obj, func){ |
| // summary |
| // Stores function refs and calls them one by one in the order they came in |
| // when load event occurs. |
| // obj: Function||Object? |
| // holder object |
| // func: Function |
| // function that will be called |
| this._pushOnStack(this._onLoadStack, obj, func); |
| }, |
| |
| addOnUnload: function(obj, func){ |
| // summary |
| // Stores function refs and calls them one by one in the order they came in |
| // when unload event occurs. |
| // obj: Function||Object |
| // holder object |
| // func: Function |
| // function that will be called |
| this._pushOnStack(this._onUnloadStack, obj, func); |
| }, |
| |
| addOnUnLoad: function(){ |
| // summary: |
| // Deprecated use addOnUnload (lower cased load) |
| dojo.deprecated(this.widgetType + ".addOnUnLoad, use addOnUnload instead. (lowercased Load)", 0.5); |
| this.addOnUnload.apply(this, arguments); |
| }, |
| |
| _pushOnStack: function(stack, obj, func){ |
| if(typeof func == 'undefined') { |
| stack.push(obj); |
| }else{ |
| stack.push(function(){ obj[func](); }); |
| } |
| }, |
| |
| destroy: function(){ |
| // make sure we call onUnload |
| this.onUnload(); |
| dojo.widget.ContentPane.superclass.destroy.call(this); |
| }, |
| |
| onExecError: function(/*Object*/e){ |
| // summary: |
| // called when content script eval error or Java error occurs, preventDefault-able |
| // default is to debug not alert as in 0.3.1 |
| }, |
| |
| onContentError: function(/*Object*/e){ |
| // summary: |
| // called on DOM faults, require fault etc in content, preventDefault-able |
| // default is to display errormessage inside pane |
| }, |
| |
| onDownloadError: function(/*Object*/e){ |
| // summary: |
| // called when download error occurs, preventDefault-able |
| // default is to display errormessage inside pane |
| }, |
| |
| onDownloadStart: function(/*Object*/e){ |
| // summary: |
| // called before download starts, preventDefault-able |
| // default is to display loadingMessage inside pane |
| // by changing e.text in your event handler you can change loading message |
| }, |
| |
| // |
| onDownloadEnd: function(url, data){ |
| // summary: |
| // called when download is finished |
| // |
| // url String: url that downloaded data |
| // data String: the markup that was downloaded |
| data = this.splitAndFixPaths(data, url); |
| this.setContent(data); |
| }, |
| |
| // useful if user wants to prevent default behaviour ie: _setContent("Error...") |
| _handleDefaults: function(e, handler, messType){ |
| if(!handler){ handler = "onContentError"; } |
| |
| if(dojo.lang.isString(e)){ e = {text: e}; } |
| |
| if(!e.text){ e.text = e.toString(); } |
| |
| e.toString = function(){ return this.text; }; |
| |
| if(typeof e.returnValue != "boolean"){ |
| e.returnValue = true; |
| } |
| if(typeof e.preventDefault != "function"){ |
| e.preventDefault = function(){ this.returnValue = false; }; |
| } |
| // call our handler |
| this[handler](e); |
| if(e.returnValue){ |
| switch(messType){ |
| case true: // fallthrough, old compat |
| case "alert": |
| alert(e.toString()); break; |
| case "debug": |
| dojo.debug(e.toString()); break; |
| default: |
| // makes sure scripts can clean up after themselves, before we setContent |
| if(this._callOnUnload){ this.onUnload(); } |
| // makes sure we dont try to call onUnLoad again on this event, |
| // ie onUnLoad before 'Loading...' but not before clearing 'Loading...' |
| this._callOnUnload = false; |
| |
| // we might end up in a endless recursion here if domNode cant append content |
| if(arguments.callee._loopStop){ |
| dojo.debug(e.toString()); |
| }else{ |
| arguments.callee._loopStop = true; |
| this._setContent(e.toString()); |
| } |
| } |
| } |
| arguments.callee._loopStop = false; |
| }, |
| |
| // pathfixes, require calls, css stuff and neccesary content clean |
| splitAndFixPaths: function(s, url){ |
| // summary: |
| // adjusts all relative paths in (hopefully) all cases, images, remote scripts, links etc. |
| // splits up content in different pieces, scripts, title, style, link and whats left becomes .xml |
| // s String: The markup in string |
| // url (String||dojo.uri.Uri?) url that pulled in markup |
| |
| var titles = [], scripts = [],tmp = [];// init vars |
| var match = [], requires = [], attr = [], styles = []; |
| var str = '', path = '', fix = '', tagFix = '', tag = '', origPath = ''; |
| |
| if(!url) { url = "./"; } // point to this page if not set |
| |
| if(s){ // make sure we dont run regexes on empty content |
| |
| /************** <title> ***********/ |
| // khtml is picky about dom faults, you can't attach a <style> or <title> node as child of body |
| // must go into head, so we need to cut out those tags |
| var regex = /<title[^>]*>([\s\S]*?)<\/title>/i; |
| while(match = regex.exec(s)){ |
| titles.push(match[1]); |
| s = s.substring(0, match.index) + s.substr(match.index + match[0].length); |
| }; |
| |
| /************** adjust paths *****************/ |
| if(this.adjustPaths){ |
| // attributepaths one tag can have multiple paths example: |
| // <input src="..." style="url(..)"/> or <a style="url(..)" href=".."> |
| // strip out the tag and run fix on that. |
| // this guarantees that we won't run replace on another tag's attribute + it was easier do |
| var regexFindTag = /<[a-z][a-z0-9]*[^>]*\s(?:(?:src|href|style)=[^>])+[^>]*>/i; |
| var regexFindAttr = /\s(src|href|style)=(['"]?)([\w()\[\]\/.,\\'"-:;#=&?\s@]+?)\2/i; |
| // these are the supported protocols, all other is considered relative |
| var regexProtocols = /^(?:[#]|(?:(?:https?|ftps?|file|javascript|mailto|news):))/; |
| |
| while(tag = regexFindTag.exec(s)){ |
| str += s.substring(0, tag.index); |
| s = s.substring((tag.index + tag[0].length), s.length); |
| tag = tag[0]; |
| |
| // loop through attributes |
| tagFix = ''; |
| while(attr = regexFindAttr.exec(tag)){ |
| path = ""; origPath = attr[3]; |
| switch(attr[1].toLowerCase()){ |
| case "src":// falltrough |
| case "href": |
| if(regexProtocols.exec(origPath)){ |
| path = origPath; |
| } else { |
| path = (new dojo.uri.Uri(url, origPath).toString()); |
| } |
| break; |
| case "style":// style |
| path = dojo.html.fixPathsInCssText(origPath, url); |
| break; |
| default: |
| path = origPath; |
| } |
| fix = " " + attr[1] + "=" + attr[2] + path + attr[2]; |
| // slices up tag before next attribute check |
| tagFix += tag.substring(0, attr.index) + fix; |
| tag = tag.substring((attr.index + attr[0].length), tag.length); |
| } |
| str += tagFix + tag; //dojo.debug(tagFix + tag); |
| } |
| s = str+s; |
| } |
| |
| /**************** cut out all <style> and <link rel="stylesheet" href=".."> **************/ |
| regex = /(?:<(style)[^>]*>([\s\S]*?)<\/style>|<link ([^>]*rel=['"]?stylesheet['"]?[^>]*)>)/i; |
| while(match = regex.exec(s)){ |
| if(match[1] && match[1].toLowerCase() == "style"){ |
| styles.push(dojo.html.fixPathsInCssText(match[2],url)); |
| }else if(attr = match[3].match(/href=(['"]?)([^'">]*)\1/i)){ |
| styles.push({path: attr[2]}); |
| } |
| s = s.substring(0, match.index) + s.substr(match.index + match[0].length); |
| }; |
| |
| /***************** cut out all <script> tags, push them into scripts array ***************/ |
| var regex = /<script([^>]*)>([\s\S]*?)<\/script>/i; |
| var regexSrc = /src=(['"]?)([^"']*)\1/i; |
| var regexDojoJs = /.*(\bdojo\b\.js(?:\.uncompressed\.js)?)$/; |
| var regexInvalid = /(?:var )?\bdjConfig\b(?:[\s]*=[\s]*\{[^}]+\}|\.[\w]*[\s]*=[\s]*[^;\n]*)?;?|dojo\.hostenv\.writeIncludes\(\s*\);?/g; |
| var regexRequires = /dojo\.(?:(?:require(?:After)?(?:If)?)|(?:widget\.(?:manager\.)?registerWidgetPackage)|(?:(?:hostenv\.)?setModulePrefix|registerModulePath)|defineNamespace)\((['"]).*?\1\)\s*;?/; |
| |
| while(match = regex.exec(s)){ |
| if(this.executeScripts && match[1]){ |
| if(attr = regexSrc.exec(match[1])){ |
| // remove a dojo.js or dojo.js.uncompressed.js from remoteScripts |
| // we declare all files named dojo.js as bad, regardless of path |
| if(regexDojoJs.exec(attr[2])){ |
| dojo.debug("Security note! inhibit:"+attr[2]+" from being loaded again."); |
| }else{ |
| scripts.push({path: attr[2]}); |
| } |
| } |
| } |
| if(match[2]){ |
| // remove all invalid variables etc like djConfig and dojo.hostenv.writeIncludes() |
| var sc = match[2].replace(regexInvalid, ""); |
| if(!sc){ continue; } |
| |
| // cut out all dojo.require (...) calls, if we have execute |
| // scripts false widgets dont get there require calls |
| // takes out possible widgetpackage registration as well |
| while(tmp = regexRequires.exec(sc)){ |
| requires.push(tmp[0]); |
| sc = sc.substring(0, tmp.index) + sc.substr(tmp.index + tmp[0].length); |
| } |
| if(this.executeScripts){ |
| scripts.push(sc); |
| } |
| } |
| s = s.substr(0, match.index) + s.substr(match.index + match[0].length); |
| } |
| |
| /********* extract content *********/ |
| if(this.extractContent){ |
| match = s.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); |
| if(match) { s = match[1]; } |
| } |
| |
| /*** replace scriptScope prefix in html Event handler |
| * working order: find tags with scriptScope in a tag attribute |
| * then replace all standalone scriptScope occurencies with reference to to this widget |
| * valid onClick="scriptScope.func()" or onClick="scriptScope['func']();scriptScope.i++" |
| * not valid onClick="var.scriptScope.ref" nor onClick="var['scriptScope'].ref" */ |
| if(this.executeScripts && this.scriptSeparation){ |
| var regex = /(<[a-zA-Z][a-zA-Z0-9]*\s[^>]*?\S=)((['"])[^>]*scriptScope[^>]*>)/; |
| var regexAttr = /([\s'";:\(])scriptScope(.*)/; // we rely on that attribute begins ' or " |
| str = ""; |
| while(tag = regex.exec(s)){ |
| tmp = ((tag[3]=="'") ? '"': "'");fix= ""; |
| str += s.substring(0, tag.index) + tag[1]; |
| while(attr = regexAttr.exec(tag[2])){ |
| tag[2] = tag[2].substring(0, attr.index) + attr[1] + "dojo.widget.byId("+ tmp + this.widgetId + tmp + ").scriptScope" + attr[2]; |
| } |
| str += tag[2]; |
| s = s.substr(tag.index + tag[0].length); |
| } |
| s = str + s; |
| } |
| } |
| |
| return {"xml": s, // Object |
| "styles": styles, |
| "titles": titles, |
| "requires": requires, |
| "scripts": scripts, |
| "url": url}; |
| }, |
| |
| |
| _setContent: function(cont){ |
| this.destroyChildren(); |
| |
| // remove old stylenodes from HEAD |
| for(var i = 0; i < this._styleNodes.length; i++){ |
| if(this._styleNodes[i] && this._styleNodes[i].parentNode){ |
| this._styleNodes[i].parentNode.removeChild(this._styleNodes[i]); |
| } |
| } |
| this._styleNodes = []; |
| |
| try{ |
| var node = this.containerNode || this.domNode; |
| while(node.firstChild){ |
| dojo.html.destroyNode(node.firstChild); |
| } |
| if(typeof cont != "string"){ |
| node.appendChild(cont); |
| }else{ |
| node.innerHTML = cont; |
| } |
| }catch(e){ |
| e.text = "Couldn't load content:"+e.description; |
| this._handleDefaults(e, "onContentError"); |
| } |
| }, |
| |
| setContent: function(data){ |
| // summary: |
| // Replaces old content with data content, include style classes from old content |
| // data String||DomNode: new content, be it Document fragment or a DomNode chain |
| // If data contains style tags, link rel=stylesheet it inserts those styles into DOM |
| this.abort(); |
| if(this._callOnUnload){ this.onUnload(); }// this tells a remote script clean up after itself |
| this._callOnUnload = true; |
| |
| if(!data || dojo.html.isNode(data)){ |
| // if we do a clean using setContent(""); or setContent(#node) bypass all parsing, extractContent etc |
| this._setContent(data); |
| this.onResized(); |
| this.onLoad(); |
| }else{ |
| // need to run splitAndFixPaths? ie. manually setting content |
| // adjustPaths is taken care of inside splitAndFixPaths |
| if(typeof data.xml != "string"){ |
| this.href = ""; // so we can refresh safely |
| data = this.splitAndFixPaths(data); |
| } |
| |
| this._setContent(data.xml); |
| |
| // insert styles from content (in same order they came in) |
| for(var i = 0; i < data.styles.length; i++){ |
| if(data.styles[i].path){ |
| this._styleNodes.push(dojo.html.insertCssFile(data.styles[i].path, dojo.doc(), false, true)); |
| }else{ |
| this._styleNodes.push(dojo.html.insertCssText(data.styles[i])); |
| } |
| } |
| |
| if(this.parseContent){ |
| for(var i = 0; i < data.requires.length; i++){ |
| try{ |
| eval(data.requires[i]); |
| } catch(e){ |
| e.text = "ContentPane: error in package loading calls, " + (e.description||e); |
| this._handleDefaults(e, "onContentError", "debug"); |
| } |
| } |
| } |
| // need to allow async load, Xdomain uses it |
| // is inline function because we cant send args to dojo.addOnLoad |
| var _self = this; |
| function asyncParse(){ |
| if(_self.executeScripts){ |
| _self._executeScripts(data.scripts); |
| } |
| |
| if(_self.parseContent){ |
| var node = _self.containerNode || _self.domNode; |
| var parser = new dojo.xml.Parse(); |
| var frag = parser.parseElement(node, null, true); |
| // createSubComponents not createComponents because frag has already been created |
| dojo.widget.getParser().createSubComponents(frag, _self); |
| } |
| |
| _self.onResized(); |
| _self.onLoad(); |
| } |
| // try as long as possible to make setContent sync call |
| if(dojo.hostenv.isXDomain && data.requires.length){ |
| dojo.addOnLoad(asyncParse); |
| }else{ |
| asyncParse(); |
| } |
| } |
| }, |
| |
| setHandler: function(/*Function*/ handler) { |
| // summary: |
| // Generate pane content from given java function |
| var fcn = dojo.lang.isFunction(handler) ? handler : window[handler]; |
| if(!dojo.lang.isFunction(fcn)) { |
| // FIXME: needs testing! somebody with java knowledge needs to try this |
| this._handleDefaults("Unable to set handler, '" + handler + "' not a function.", "onExecError", true); |
| return; |
| } |
| this.handler = function() { |
| return fcn.apply(this, arguments); |
| } |
| }, |
| |
| _runHandler: function() { |
| var ret = true; |
| if(dojo.lang.isFunction(this.handler)) { |
| this.handler(this, this.domNode); |
| ret = false; |
| } |
| this.onLoad(); |
| return ret; |
| }, |
| |
| _executeScripts: function(scripts) { |
| // loop through the scripts in the order they came in |
| var self = this; |
| var tmp = "", code = ""; |
| for(var i = 0; i < scripts.length; i++){ |
| if(scripts[i].path){ // remotescript |
| dojo.io.bind(this._cacheSetting({ |
| "url": scripts[i].path, |
| "load": function(type, scriptStr){ |
| dojo.lang.hitch(self, tmp = ";"+scriptStr); |
| }, |
| "error": function(type, error){ |
| error.text = type + " downloading remote script"; |
| self._handleDefaults.call(self, error, "onExecError", "debug"); |
| }, |
| "mimetype": "text/plain", |
| "sync": true |
| }, this.cacheContent)); |
| code += tmp; |
| }else{ |
| code += scripts[i]; |
| } |
| } |
| |
| |
| try{ |
| if(this.scriptSeparation){ |
| // initialize a new anonymous container for our script, dont make it part of this widgets scope chain |
| // instead send in a variable that points to this widget, useful to connect events to onLoad, onUnload etc.. |
| delete this.scriptScope; |
| this.scriptScope = new (new Function('_container_', code+'; return this;'))(self); |
| }else{ |
| // exec in global, lose the _container_ feature |
| var djg = dojo.global(); |
| if(djg.execScript){ |
| djg.execScript(code); |
| }else{ |
| var djd = dojo.doc(); |
| var sc = djd.createElement("script"); |
| sc.appendChild(djd.createTextNode(code)); |
| (this.containerNode||this.domNode).appendChild(sc); |
| } |
| } |
| }catch(e){ |
| e.text = "Error running scripts from content:\n"+e.description; |
| this._handleDefaults(e, "onExecError", "debug"); |
| } |
| } |
| } |
| ); |
| |
| dojo.require("dojo.html.common"); |
| dojo.provide("dojo.html.selection"); |
| |
| dojo.require("dojo.dom"); |
| dojo.require("dojo.lang.common"); |
| |
| /** |
| * type of selection |
| **/ |
| dojo.html.selectionType = { |
| NONE : 0, //selection is empty |
| TEXT : 1, //selection contains text (may also contains CONTROL objects) |
| CONTROL : 2 //only one element is selected (such as img, table etc) |
| }; |
| |
| dojo.html.clearSelection = function(){ |
| // summary: deselect the current selection to make it empty |
| var _window = dojo.global(); |
| var _document = dojo.doc(); |
| try{ |
| if(_window["getSelection"]){ |
| if(dojo.render.html.safari){ |
| // pulled from WebCore/ecma/kjs_window.cpp, line 2536 |
| _window.getSelection().collapse(); |
| }else{ |
| _window.getSelection().removeAllRanges(); |
| } |
| }else if(_document.selection){ |
| if(_document.selection.empty){ |
| _document.selection.empty(); |
| }else if(_document.selection.clear){ |
| _document.selection.clear(); |
| } |
| } |
| return true; |
| }catch(e){ |
| dojo.debug(e); |
| return false; |
| } |
| } |
| |
| dojo.html.disableSelection = function(/*DomNode*/element){ |
| // summary: disable selection on a node |
| element = dojo.byId(element)||dojo.body(); |
| var h = dojo.render.html; |
| |
| if(h.mozilla){ |
| element.style.MozUserSelect = "none"; |
| }else if(h.safari){ |
| element.style.KhtmlUserSelect = "none"; |
| }else if(h.ie){ |
| element.unselectable = "on"; |
| }else{ |
| return false; |
| } |
| return true; |
| } |
| |
| dojo.html.enableSelection = function(/*DomNode*/element){ |
| // summary: enable selection on a node |
| element = dojo.byId(element)||dojo.body(); |
| |
| var h = dojo.render.html; |
| if(h.mozilla){ |
| element.style.MozUserSelect = ""; |
| }else if(h.safari){ |
| element.style.KhtmlUserSelect = ""; |
| }else if(h.ie){ |
| element.unselectable = "off"; |
| }else{ |
| return false; |
| } |
| return true; |
| } |
| |
| dojo.html.selectElement = function(/*DomNode*/element){ |
| dojo.deprecated("dojo.html.selectElement", "replaced by dojo.html.selection.selectElementChildren", 0.5); |
| } |
| |
| dojo.html.selectInputText = function(/*DomNode*/element){ |
| // summary: select all the text in an input element |
| var _window = dojo.global(); |
| var _document = dojo.doc(); |
| element = dojo.byId(element); |
| if(_document["selection"] && dojo.body()["createTextRange"]){ // IE |
| var range = element.createTextRange(); |
| range.moveStart("character", 0); |
| range.moveEnd("character", element.value.length); |
| range.select(); |
| }else if(_window["getSelection"]){ |
| var selection = _window.getSelection(); |
| // FIXME: does this work on Safari? |
| element.setSelectionRange(0, element.value.length); |
| } |
| element.focus(); |
| } |
| |
| |
| dojo.html.isSelectionCollapsed = function(){ |
| dojo.deprecated("dojo.html.isSelectionCollapsed", "replaced by dojo.html.selection.isCollapsed", 0.5); |
| return dojo.html.selection.isCollapsed(); |
| } |
| |
| dojo.lang.mixin(dojo.html.selection, { |
| getType: function() { |
| // summary: Get the selection type (like document.select.type in IE). |
| if(dojo.doc()["selection"]){ //IE |
| return dojo.html.selectionType[dojo.doc().selection.type.toUpperCase()]; |
| }else{ |
| var stype = dojo.html.selectionType.TEXT; |
| |
| // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...). |
| var oSel; |
| try {oSel = dojo.global().getSelection();} |
| catch (e) {} |
| |
| if(oSel && oSel.rangeCount==1){ |
| var oRange = oSel.getRangeAt(0); |
| if (oRange.startContainer == oRange.endContainer && (oRange.endOffset - oRange.startOffset) == 1 |
| && oRange.startContainer.nodeType != dojo.dom.TEXT_NODE) { |
| stype = dojo.html.selectionType.CONTROL; |
| } |
| } |
| return stype; |
| } |
| }, |
| isCollapsed: function() { |
| // summary: return whether the current selection is empty |
| var _window = dojo.global(); |
| var _document = dojo.doc(); |
| if(_document["selection"]){ // IE |
| return _document.selection.createRange().text == ""; |
| }else if(_window["getSelection"]){ |
| var selection = _window.getSelection(); |
| if(dojo.lang.isString(selection)){ // Safari |
| return selection == ""; |
| }else{ // Mozilla/W3 |
| return selection.isCollapsed || selection.toString() == ""; |
| } |
| } |
| }, |
| getSelectedElement: function() { |
| // summary: |
| // Retrieves the selected element (if any), just in the case that a single |
| // element (object like and image or a table) is selected. |
| if ( dojo.html.selection.getType() == dojo.html.selectionType.CONTROL ){ |
| if(dojo.doc()["selection"]){ //IE |
| var range = dojo.doc().selection.createRange(); |
| |
| if ( range && range.item ){ |
| return dojo.doc().selection.createRange().item(0); |
| } |
| }else{ |
| var selection = dojo.global().getSelection(); |
| return selection.anchorNode.childNodes[ selection.anchorOffset ]; |
| } |
| } |
| }, |
| getParentElement: function() { |
| // summary: |
| // Get the parent element of the current selection |
| if(dojo.html.selection.getType() == dojo.html.selectionType.CONTROL){ |
| var p = dojo.html.selection.getSelectedElement(); |
| if(p){ return p.parentNode; } |
| }else{ |
| if(dojo.doc()["selection"]){ //IE |
| return dojo.doc().selection.createRange().parentElement(); |
| }else{ |
| var selection = dojo.global().getSelection(); |
| if(selection){ |
| var node = selection.anchorNode; |
| |
| while ( node && node.nodeType != dojo.dom.ELEMENT_NODE ){ |
| node = node.parentNode; |
| } |
| |
| return node; |
| } |
| } |
| } |
| }, |
| getSelectedText: function(){ |
| // summary: |
| // Return the text (no html tags) included in the current selection or null if no text is selected |
| if(dojo.doc()["selection"]){ //IE |
| if(dojo.html.selection.getType() == dojo.html.selectionType.CONTROL){ |
| return null; |
| } |
| return dojo.doc().selection.createRange().text; |
| }else{ |
| var selection = dojo.global().getSelection(); |
| if(selection){ |
| return selection.toString(); |
| } |
| } |
| }, |
| getSelectedHtml: function(){ |
| // summary: |
| // Return the html of the current selection or null if unavailable |
| if(dojo.doc()["selection"]){ //IE |
| if(dojo.html.selection.getType() == dojo.html.selectionType.CONTROL){ |
| return null; |
| } |
| return dojo.doc().selection.createRange().htmlText; |
| }else{ |
| var selection = dojo.global().getSelection(); |
| if(selection && selection.rangeCount){ |
| var frag = selection.getRangeAt(0).cloneContents(); |
| var div = document.createElement("div"); |
| div.appendChild(frag); |
| return div.innerHTML; |
| } |
| return null; |
| } |
| }, |
| hasAncestorElement: function(/*String*/tagName /* ... */){ |
| // summary: |
| // Check whether current selection has a parent element which is of type tagName (or one of the other specified tagName) |
| return (dojo.html.selection.getAncestorElement.apply(this, arguments) != null); |
| }, |
| getAncestorElement: function(/*String*/tagName /* ... */){ |
| // summary: |
| // Return the parent element of the current selection which is of type tagName (or one of the other specified tagName) |
| var node = dojo.html.selection.getSelectedElement() || dojo.html.selection.getParentElement(); |
| while(node /*&& node.tagName.toLowerCase() != 'body'*/){ |
| if(dojo.html.selection.isTag(node, arguments).length>0){ |
| return node; |
| } |
| node = node.parentNode; |
| } |
| return null; |
| }, |
| //modified from dojo.html.isTag to take an array as second parameter |
| isTag: function(/*DomNode*/node, /*Array*/tags) { |
| if(node && node.tagName) { |
| for (var i=0; i<tags.length; i++){ |
| if (node.tagName.toLowerCase()==String(tags[i]).toLowerCase()){ |
| return String(tags[i]).toLowerCase(); |
| } |
| } |
| } |
| return ""; |
| }, |
| selectElement: function(/*DomNode*/element) { |
| // summary: clear previous selection and select element (including all its children) |
| var _window = dojo.global(); |
| var _document = dojo.doc(); |
| element = dojo.byId(element); |
| if(_document.selection && dojo.body().createTextRange){ // IE |
| try{ |
| var range = dojo.body().createControlRange(); |
| range.addElement(element); |
| range.select(); |
| }catch(e){ |
| dojo.html.selection.selectElementChildren(element); |
| } |
| }else if(_window["getSelection"]){ |
| var selection = _window.getSelection(); |
| // FIXME: does this work on Safari? |
| if(selection["removeAllRanges"]){ // Mozilla |
| var range = _document.createRange() ; |
| range.selectNode(element) ; |
| selection.removeAllRanges() ; |
| selection.addRange(range) ; |
| } |
| } |
| }, |
| selectElementChildren: function(/*DomNode*/element){ |
| // summary: clear previous selection and select the content of the node (excluding the node itself) |
| var _window = dojo.global(); |
| var _document = dojo.doc(); |
| element = dojo.byId(element); |
| if(_document.selection && dojo.body().createTextRange){ // IE |
| var range = dojo.body().createTextRange(); |
| range.moveToElementText(element); |
| range.select(); |
| }else if(_window["getSelection"]){ |
| var selection = _window.getSelection(); |
| if(selection["setBaseAndExtent"]){ // Safari |
| selection.setBaseAndExtent(element, 0, element, element.innerText.length - 1); |
| } else if(selection["selectAllChildren"]){ // Mozilla |
| selection.selectAllChildren(element); |
| } |
| } |
| }, |
| getBookmark: function(){ |
| // summary: Retrieves a bookmark that can be used with moveToBookmark to return to the same range |
| var bookmark; |
| var _document = dojo.doc(); |
| if(_document["selection"]){ // IE |
| var range = _document.selection.createRange(); |
| bookmark = range.getBookmark(); |
| }else{ |
| var selection; |
| try {selection = dojo.global().getSelection();} |
| catch (e) {} |
| if(selection){ |
| var range = selection.getRangeAt(0); |
| bookmark = range.cloneRange(); |
| }else{ |
| dojo.debug("No idea how to store the current selection for this browser!"); |
| } |
| } |
| return bookmark; |
| }, |
| moveToBookmark: function(/*Object*/bookmark){ |
| // summary: Moves current selection to a bookmark |
| // bookmark: this should be a returned object from dojo.html.selection.getBookmark() |
| var _document = dojo.doc(); |
| if(_document["selection"]){ // IE |
| var range = _document.selection.createRange(); |
| range.moveToBookmark(bookmark); |
| range.select(); |
| }else{ //Moz/W3C |
| var selection; |
| try {selection = dojo.global().getSelection();} |
| catch (e) {} |
| if(selection && selection['removeAllRanges']){ |
| selection.removeAllRanges() ; |
| selection.addRange(bookmark) ; |
| }else{ |
| dojo.debug("No idea how to restore selection for this browser!"); |
| } |
| } |
| }, |
| collapse: function(/*Boolean*/beginning) { |
| // summary: clear current selection |
| if(dojo.global()['getSelection']){ |
| var selection = dojo.global().getSelection(); |
| if(selection.removeAllRanges){ // Mozilla |
| if(beginning){ |
| selection.collapseToStart(); |
| }else{ |
| selection.collapseToEnd(); |
| } |
| }else{ // Safari |
| // pulled from WebCore/ecma/kjs_window.cpp, line 2536 |
| dojo.global().getSelection().collapse(beginning); |
| } |
| }else if(dojo.doc().selection){ // IE |
| var range = dojo.doc().selection.createRange(); |
| range.collapse(beginning); |
| range.select(); |
| } |
| }, |
| remove: function() { |
| // summary: delete current selection |
| if(dojo.doc().selection) { //IE |
| var selection = dojo.doc().selection; |
| |
| if ( selection.type.toUpperCase() != "NONE" ){ |
| selection.clear(); |
| } |
| |
| return selection; |
| }else{ |
| var selection = dojo.global().getSelection(); |
| |
| for ( var i = 0; i < selection.rangeCount; i++ ){ |
| selection.getRangeAt(i).deleteContents(); |
| } |
| |
| return selection; |
| } |
| } |
| }); |
| |
| dojo.provide("dojo.html.iframe"); |
| |
| |
| // thanks burstlib! |
| dojo.html.iframeContentWindow = function(/* HTMLIFrameElement */iframe_el) { |
| // summary |
| // returns the window reference of the passed iframe |
| var win = dojo.html.getDocumentWindow(dojo.html.iframeContentDocument(iframe_el)) || |
| // Moz. TODO: is this available when defaultView isn't? |
| dojo.html.iframeContentDocument(iframe_el).__parent__ || |
| (iframe_el.name && document.frames[iframe_el.name]) || null; |
| return win; // Window |
| } |
| |
| dojo.html.iframeContentDocument = function(/* HTMLIFrameElement */iframe_el){ |
| // summary |
| // returns a reference to the document object inside iframe_el |
| var doc = iframe_el.contentDocument // W3 |
| || ((iframe_el.contentWindow)&&(iframe_el.contentWindow.document)) // IE |
| || ((iframe_el.name)&&(document.frames[iframe_el.name])&&(document.frames[iframe_el.name].document)) |
| || null; |
| return doc; // HTMLDocument |
| } |
| |
| dojo.html.BackgroundIframe = function(/* HTMLElement */node) { |
| // summary |
| // For IE z-index schenanigans |
| // Two possible uses: |
| // 1. new dojo.html.BackgroundIframe(node) |
| // Makes a background iframe as a child of node, that fills area (and position) of node |
| // 2. new dojo.html.BackgroundIframe() |
| // Attaches frame to dojo.body(). User must call size() to set size. |
| if(dojo.render.html.ie55 || dojo.render.html.ie60) { |
| var html="<iframe src='javascript:false'" |
| + " style='position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;" |
| + "z-index: -1; filter:Alpha(Opacity=\"0\");' " |
| + ">"; |
| this.iframe = dojo.doc().createElement(html); |
| this.iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didnt work. |
| if(node){ |
| node.appendChild(this.iframe); |
| this.domNode=node; |
| }else{ |
| dojo.body().appendChild(this.iframe); |
| this.iframe.style.display="none"; |
| } |
| } |
| } |
| dojo.lang.extend(dojo.html.BackgroundIframe, { |
| iframe: null, |
| onResized: function(){ |
| // summary |
| // Resize event handler. |
| // TODO: this function shouldn't be necessary but setting width=height=100% doesn't work! |
| if(this.iframe && this.domNode && this.domNode.parentNode){ // No parentElement if onResized() timeout event occurs on a removed domnode |
| var outer = dojo.html.getMarginBox(this.domNode); |
| if (outer.width == 0 || outer.height == 0 ){ |
| dojo.lang.setTimeout(this, this.onResized, 100); |
| return; |
| } |
| this.iframe.style.width = outer.width + "px"; |
| this.iframe.style.height = outer.height + "px"; |
| } |
| }, |
| |
| size: function(/* HTMLElement */node) { |
| // summary: |
| // Call this function if the iframe is connected to dojo.body() |
| // rather than the node being shadowed |
| |
| // (TODO: erase) |
| if(!this.iframe){ return; } |
| var coords = dojo.html.toCoordinateObject(node, true, dojo.html.boxSizing.BORDER_BOX); |
| with(this.iframe.style){ |
| width = coords.width + "px"; |
| height = coords.height + "px"; |
| left = coords.left + "px"; |
| top = coords.top + "px"; |
| } |
| }, |
| |
| setZIndex: function(/* HTMLElement */node){ |
| // summary |
| // Sets the z-index of the background iframe. |
| if(!this.iframe){ return; } |
| if(dojo.dom.isNode(node)){ |
| this.iframe.style.zIndex = dojo.html.getStyle(node, "z-index") - 1; |
| }else if(!isNaN(node)){ |
| this.iframe.style.zIndex = node; |
| } |
| }, |
| |
| show: function(){ |
| // summary: |
| // show the iframe |
| if(this.iframe){ |
| this.iframe.style.display = "block"; |
| } |
| }, |
| |
| hide: function(){ |
| // summary: |
| // hide the iframe |
| if(this.iframe){ |
| this.iframe.style.display = "none"; |
| } |
| }, |
| |
| remove: function(){ |
| // summary: |
| // remove the iframe |
| if(this.iframe){ |
| dojo.html.removeNode(this.iframe, true); |
| delete this.iframe; |
| this.iframe=null; |
| } |
| } |
| }); |
| |
| dojo.provide("dojo.widget.PopupContainer"); |
| |
| dojo.require("dojo.html.style"); |
| |
| |
| |
| dojo.require("dojo.event.*"); |
| |
| |
| |
| dojo.declare( |
| "dojo.widget.PopupContainerBase", |
| null, |
| function(){ |
| this.queueOnAnimationFinish = []; |
| }, |
| { |
| // summary: |
| // PopupContainerBase is the mixin class which provide popup behaviors: |
| // it can open in a given position x,y or around a given node. |
| // In addition, it handles animation and IE bleed through workaround. |
| // description: |
| // This class can not be used standalone: it should be mixed-in to a |
| // dojo.widget.HtmlWidget. Use PopupContainer instead if you want a |
| // a standalone popup widget |
| |
| // isShowingNow: Boolean: whether this popup is shown |
| isShowingNow: false, |
| |
| // currentSubpopup: Widget: the shown sub popup if any |
| currentSubpopup: null, |
| |
| // beginZIndex: Integer: the minimal popup zIndex |
| beginZIndex: 1000, |
| |
| // parentPopup: Widget: parent popup widget |
| parentPopup: null, |
| |
| // parent: Widget: the widget that caused me to be displayed; the logical parent. |
| parent: null, |
| |
| // popupIndex: Integer: level of sub popup |
| popupIndex: 0, |
| |
| // aroundBox: dojo.html.boxSizing: which bounding box to use for open aroundNode. By default use BORDER box of the aroundNode |
| aroundBox: dojo.html.boxSizing.BORDER_BOX, |
| |
| // openedForWindow: Object: in which window the open() is triggered |
| openedForWindow: null, |
| |
| processKey: function(/*Event*/evt){ |
| // summary: key event handler |
| return false; |
| }, |
| |
| applyPopupBasicStyle: function(){ |
| // summary: apply necessary css rules to the top domNode |
| // description: |
| // this function should be called in sub class where a custom |
| // templateString/templateStringPath is used (see Tooltip widget) |
| with(this.domNode.style){ |
| display = 'none'; |
| position = 'absolute'; |
| } |
| }, |
| |
| aboutToShow: function() { |
| // summary: connect to this stub to modify the content of the popup |
| }, |
| |
| open: function(/*Integer*/x, /*Integer*/y, /*DomNode*/parent, /*Object*/explodeSrc, /*String?*/orient, /*Array?*/padding){ |
| // summary: |
| // Open the popup at position (x,y), relative to dojo.body() |
| // Or open(node, parent, explodeSrc, aroundOrient) to open |
| // around node |
| if (this.isShowingNow){ return; } |
| |
| // if I click right button and menu is opened, then it gets 2 commands: close -> open |
| // so close enables animation and next "open" is put to queue to occur at new location |
| if(this.animationInProgress){ |
| this.queueOnAnimationFinish.push(this.open, arguments); |
| return; |
| } |
| |
| this.aboutToShow(); |
| |
| var around = false, node, aroundOrient; |
| if(typeof x == 'object'){ |
| node = x; |
| aroundOrient = explodeSrc; |
| explodeSrc = parent; |
| parent = y; |
| around = true; |
| } |
| |
| // save this so that the focus can be returned |
| this.parent = parent; |
| |
| // for unknown reasons even if the domNode is attached to the body in postCreate(), |
| // it's not attached here, so have to attach it here. |
| dojo.body().appendChild(this.domNode); |
| |
| // if explodeSrc isn't specified then explode from my parent widget |
| explodeSrc = explodeSrc || parent["domNode"] || []; |
| |
| //keep track of parent popup to decided whether this is a top level popup |
| var parentPopup = null; |
| this.isTopLevel = true; |
| while(parent){ |
| if(parent !== this && (parent.setOpenedSubpopup != undefined && parent.applyPopupBasicStyle != undefined)){ |
| parentPopup = parent; |
| this.isTopLevel = false; |
| parentPopup.setOpenedSubpopup(this); |
| break; |
| } |
| parent = parent.parent; |
| } |
| |
| this.parentPopup = parentPopup; |
| this.popupIndex = parentPopup ? parentPopup.popupIndex + 1 : 1; |
| |
| if(this.isTopLevel){ |
| var button = dojo.html.isNode(explodeSrc) ? explodeSrc : null; |
| dojo.widget.PopupManager.opened(this, button); |
| } |
| |
| //Store the current selection and restore it before the action for a menu item |
| //is executed. This is required as clicking on an menu item deselects current selection |
| if(this.isTopLevel && !dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.isCollapsed)){ |
| this._bookmark = dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.getBookmark); |
| }else{ |
| this._bookmark = null; |
| } |
| |
| //convert explodeSrc from format [x, y] to |
| //{left: x, top: y, width: 0, height: 0} which is the new |
| //format required by dojo.html.toCoordinateObject |
| if(explodeSrc instanceof Array){ |
| explodeSrc = {left: explodeSrc[0], top: explodeSrc[1], width: 0, height: 0}; |
| } |
| |
| // display temporarily, and move into position, then hide again |
| with(this.domNode.style){ |
| display=""; |
| zIndex = this.beginZIndex + this.popupIndex; |
| } |
| |
| if(around){ |
| this.move(node, padding, aroundOrient); |
| }else{ |
| this.move(x, y, padding, orient); |
| } |
| this.domNode.style.display="none"; |
| |
| this.explodeSrc = explodeSrc; |
| |
| // then use the user defined method to display it |
| this.show(); |
| |
| this.isShowingNow = true; |
| }, |
| |
| // TODOC: move(node, padding, aroundOrient) how to do this? |
| move: function(/*Int*/x, /*Int*/y, /*Integer?*/padding, /*String?*/orient){ |
| // summary: calculate where to place the popup |
| |
| var around = (typeof x == "object"); |
| if(around){ |
| var aroundOrient=padding; |
| var node=x; |
| padding=y; |
| if(!aroundOrient){ //By default, attempt to open above the aroundNode, or below |
| aroundOrient = {'BL': 'TL', 'TL': 'BL'}; |
| } |
| dojo.html.placeOnScreenAroundElement(this.domNode, node, padding, this.aroundBox, aroundOrient); |
| }else{ |
| if(!orient){ orient = 'TL,TR,BL,BR';} |
| dojo.html.placeOnScreen(this.domNode, x, y, padding, true, orient); |
| } |
| }, |
| |
| close: function(/*Boolean?*/force){ |
| // summary: hide the popup |
| if(force){ |
| this.domNode.style.display="none"; |
| } |
| |
| // If we are in the process of opening the menu and we are asked to close it |
| if(this.animationInProgress){ |
| this.queueOnAnimationFinish.push(this.close, []); |
| return; |
| } |
| |
| this.closeSubpopup(force); |
| this.hide(); |
| if(this.bgIframe){ |
| this.bgIframe.hide(); |
| this.bgIframe.size({left: 0, top: 0, width: 0, height: 0}); |
| } |
| if(this.isTopLevel){ |
| dojo.widget.PopupManager.closed(this); |
| } |
| this.isShowingNow = false; |
| // return focus to the widget that opened the menu |
| |
| if(this.parent){ |
| setTimeout( |
| dojo.lang.hitch(this, |
| function(){ |
| try{ |
| if(this.parent['focus']){ |
| this.parent.focus(); |
| }else{ |
| this.parent.domNode.focus(); |
| } |
| }catch(e){dojo.debug("No idea how to focus to parent", e);} |
| } |
| ), |
| 10 |
| ); |
| } |
| |
| |
| //do not need to restore if current selection is not empty |
| //(use keyboard to select a menu item) |
| if(this._bookmark && dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.isCollapsed)){ |
| if(this.openedForWindow){ |
| this.openedForWindow.focus() |
| } |
| try{ |
| dojo.withGlobal(this.openedForWindow||dojo.global(), "moveToBookmark", dojo.html.selection, [this._bookmark]); |
| }catch(e){ |
| /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */ |
| } |
| } |
| this._bookmark = null; |
| }, |
| |
| closeAll: function(/*Boolean?*/force){ |
| // summary: hide all popups including sub ones |
| if (this.parentPopup){ |
| this.parentPopup.closeAll(force); |
| }else{ |
| this.close(force); |
| } |
| }, |
| |
| setOpenedSubpopup: function(/*Widget*/popup) { |
| // summary: used by sub popup to set currentSubpopup in the parent popup |
| this.currentSubpopup = popup; |
| }, |
| |
| closeSubpopup: function(/*Boolean?*/force) { |
| // summary: close opened sub popup |
| if(this.currentSubpopup == null){ return; } |
| |
| this.currentSubpopup.close(force); |
| this.currentSubpopup = null; |
| }, |
| |
| onShow: function() { |
| dojo.widget.PopupContainer.superclass.onShow.apply(this, arguments); |
| // With some animation (wipe), after close, the size of the domnode is 0 |
| // and next time when shown, the open() function can not determine |
| // the correct place to popup, so we store the opened size here and |
| // set it after close (in function onHide()) |
| this.openedSize={w: this.domNode.style.width, h: this.domNode.style.height}; |
| // prevent IE bleed through |
| if(dojo.render.html.ie){ |
| if(!this.bgIframe){ |
| this.bgIframe = new dojo.html.BackgroundIframe(); |
| this.bgIframe.setZIndex(this.domNode); |
| } |
| |
| this.bgIframe.size(this.domNode); |
| this.bgIframe.show(); |
| } |
| this.processQueue(); |
| }, |
| |
| processQueue: function() { |
| // summary: do events from queue |
| if (!this.queueOnAnimationFinish.length) return; |
| |
| var func = this.queueOnAnimationFinish.shift(); |
| var args = this.queueOnAnimationFinish.shift(); |
| |
| func.apply(this, args); |
| }, |
| |
| onHide: function() { |
| dojo.widget.HtmlWidget.prototype.onHide.call(this); |
| |
| //restore size of the domnode, see comment in |
| //function onShow() |
| if(this.openedSize){ |
| with(this.domNode.style){ |
| width=this.openedSize.w; |
| height=this.openedSize.h; |
| } |
| } |
| |
| this.processQueue(); |
| } |
| }); |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.PopupContainer", |
| [dojo.widget.HtmlWidget, dojo.widget.PopupContainerBase], { |
| // summary: dojo.widget.PopupContainer is the widget version of dojo.widget.PopupContainerBase |
| isContainer: true, |
| fillInTemplate: function(){ |
| this.applyPopupBasicStyle(); |
| dojo.widget.PopupContainer.superclass.fillInTemplate.apply(this, arguments); |
| } |
| }); |
| |
| |
| dojo.widget.PopupManager = new function(){ |
| // summary: |
| // the popup manager makes sure we don't have several popups |
| // open at once. the root popup in an opening sequence calls |
| // opened(). when a root menu closes it calls closed(). then |
| // everything works. lovely. |
| |
| this.currentMenu = null; |
| this.currentButton = null; // button that opened current menu (if any) |
| this.currentFocusMenu = null; // the (sub)menu which receives key events |
| this.focusNode = null; |
| this.registeredWindows = []; |
| |
| this.registerWin = function(/*Window*/win){ |
| // summary: register a window so that when clicks/scroll in it, the popup can be closed automatically |
| if(!win.__PopupManagerRegistered) |
| { |
| dojo.event.connect(win.document, 'onmousedown', this, 'onClick'); |
| dojo.event.connect(win, "onscroll", this, "onClick"); |
| dojo.event.connect(win.document, "onkey", this, 'onKey'); |
| win.__PopupManagerRegistered = true; |
| this.registeredWindows.push(win); |
| } |
| }; |
| |
| /* |
| |
| */ |
| this.registerAllWindows = function(/*Window*/targetWindow){ |
| // summary: |
| // This function register all the iframes and the top window, |
| // so that whereever the user clicks in the page, the popup |
| // menu will be closed |
| // In case you add an iframe after onload event, please call |
| // dojo.widget.PopupManager.registerWin manually |
| |
| //starting from window.top, clicking everywhere in this page |
| //should close popup menus |
| if(!targetWindow) { //see comment below |
| targetWindow = dojo.html.getDocumentWindow(window.top && window.top.document || window.document); |
| } |
| |
| this.registerWin(targetWindow); |
| |
| for (var i = 0; i < targetWindow.frames.length; i++){ |
| try{ |
| //do not remove dojo.html.getDocumentWindow, see comment in it |
| var win = dojo.html.getDocumentWindow(targetWindow.frames[i].document); |
| if(win){ |
| this.registerAllWindows(win); |
| } |
| }catch(e){ /* squelch error for cross domain iframes */ } |
| } |
| }; |
| |
| this.unRegisterWin = function(/*Window*/win){ |
| // summary: remove listeners on the registered window |
| if(win.__PopupManagerRegistered) |
| { |
| dojo.event.disconnect(win.document, 'onmousedown', this, 'onClick'); |
| dojo.event.disconnect(win, "onscroll", this, "onClick"); |
| dojo.event.disconnect(win.document, "onkey", this, 'onKey'); |
| win.__PopupManagerRegistered = false; |
| } |
| }; |
| |
| this.unRegisterAllWindows = function(){ |
| // summary: remove listeners on all the registered windows |
| for(var i=0;i<this.registeredWindows.length;++i){ |
| this.unRegisterWin(this.registeredWindows[i]); |
| } |
| this.registeredWindows = []; |
| }; |
| |
| dojo.addOnLoad(this, "registerAllWindows"); |
| dojo.addOnUnload(this, "unRegisterAllWindows"); |
| |
| this.closed = function(/*Widget*/menu){ |
| // summary: notify the manager that menu is closed |
| if (this.currentMenu == menu){ |
| this.currentMenu = null; |
| this.currentButton = null; |
| this.currentFocusMenu = null; |
| } |
| }; |
| |
| this.opened = function(/*Widget*/menu, /*DomNode*/button){ |
| // summary: sets the current opened popup |
| if (menu == this.currentMenu){ return; } |
| |
| if (this.currentMenu){ |
| this.currentMenu.close(); |
| } |
| |
| this.currentMenu = menu; |
| this.currentFocusMenu = menu; |
| this.currentButton = button; |
| }; |
| |
| this.setFocusedMenu = function(/*Widget*/menu){ |
| // summary: |
| // Set the current focused popup, This is used by popups which supports keyboard navigation |
| this.currentFocusMenu = menu; |
| }; |
| |
| this.onKey = function(/*Event*/e){ |
| if (!e.key) { return; } |
| if(!this.currentMenu || !this.currentMenu.isShowingNow){ return; } |
| |
| // loop from child menu up ancestor chain, ending at button that spawned the menu |
| var m = this.currentFocusMenu; |
| while (m){ |
| if(m.processKey(e)){ |
| e.preventDefault(); |
| e.stopPropagation(); |
| break; |
| } |
| m = m.parentPopup || m.parentMenu; |
| } |
| }, |
| |
| this.onClick = function(/*Event*/e){ |
| if (!this.currentMenu){ return; } |
| |
| var scrolloffset = dojo.html.getScroll().offset; |
| |
| // starting from the base menu, perform a hit test |
| // and exit when one succeeds |
| |
| var m = this.currentMenu; |
| |
| while (m){ |
| if(dojo.html.overElement(m.domNode, e) || dojo.html.isDescendantOf(e.target, m.domNode)){ |
| return; |
| } |
| m = m.currentSubpopup; |
| } |
| |
| // Also, if user clicked the button that opened this menu, then |
| // that button will send the menu a close() command, so this code |
| // shouldn't try to close the menu. Closing twice messes up animation. |
| if (this.currentButton && dojo.html.overElement(this.currentButton, e)){ |
| return; |
| } |
| |
| // the click didn't fall within the open menu tree |
| // so close it |
| |
| this.currentMenu.closeAll(true); |
| }; |
| } |
| |
| dojo.provide("dojo.widget.DropdownContainer"); |
| |
| |
| |
| dojo.require("dojo.event.*"); |
| |
| dojo.require("dojo.html.display"); |
| |
| |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.DropdownContainer", |
| dojo.widget.HtmlWidget, |
| { |
| // summary: |
| // provides an input box and a button for a dropdown. |
| // In subclass, the dropdown can be specified. |
| |
| // inputWidth: String: width of the input box |
| inputWidth: "7em", |
| |
| // id: String: id of this widget |
| id: "", |
| |
| // inputId: String: id of the input box |
| inputId: "", |
| |
| // inputName: String: name of the input box |
| inputName: "", |
| |
| // iconURL: dojo.uri.Uri: icon for the dropdown button |
| iconURL: dojo.uri.moduleUri("dojo.widget", "templates/images/combo_box_arrow.png"), |
| |
| // copyClass: |
| // should we use the class properties on the source node instead |
| // of our own styles? |
| copyClasses: false, |
| |
| // iconAlt: dojo.uri.Uri: alt text for the dropdown button icon |
| iconAlt: "", |
| |
| // containerToggle: String: toggle property of the dropdown |
| containerToggle: "plain", |
| |
| // containerToggleDuration: Integer: toggle duration property of the dropdown |
| containerToggleDuration: 150, |
| |
| templateString: '<span style="white-space:nowrap"><input type="hidden" name="" value="" dojoAttachPoint="valueNode" /><input name="" type="text" value="" style="vertical-align:middle;" dojoAttachPoint="inputNode" autocomplete="off" /> <img src="${this.iconURL}" alt="${this.iconAlt}" dojoAttachEvent="onclick:onIconClick" dojoAttachPoint="buttonNode" style="vertical-align:middle; cursor:pointer; cursor:hand" /></span>', |
| templateCssPath: "", |
| isContainer: true, |
| |
| attachTemplateNodes: function(){ |
| // summary: use attachTemplateNodes to specify containerNode, as fillInTemplate is too late for this |
| dojo.widget.DropdownContainer.superclass.attachTemplateNodes.apply(this, arguments); |
| this.popup = dojo.widget.createWidget("PopupContainer", {toggle: this.containerToggle, toggleDuration: this.containerToggleDuration}); |
| this.containerNode = this.popup.domNode; |
| }, |
| |
| fillInTemplate: function(args, frag){ |
| this.domNode.appendChild(this.popup.domNode); |
| if(this.id) { this.domNode.id = this.id; } |
| if(this.inputId){ this.inputNode.id = this.inputId; } |
| if(this.inputName){ this.inputNode.name = this.inputName; } |
| this.inputNode.style.width = this.inputWidth; |
| this.inputNode.disabled = this.disabled; |
| |
| if(this.copyClasses){ |
| this.inputNode.style = ""; |
| this.inputNode.className = this.getFragNodeRef(frag).className; |
| } |
| |
| |
| dojo.event.connect(this.inputNode, "onchange", this, "onInputChange"); |
| }, |
| |
| onIconClick: function(/*Event*/ evt){ |
| if(this.disabled) return; |
| if(!this.popup.isShowingNow){ |
| this.popup.open(this.inputNode, this, this.buttonNode); |
| }else{ |
| this.popup.close(); |
| } |
| }, |
| |
| hideContainer: function(){ |
| // summary: hide the dropdown |
| if(this.popup.isShowingNow){ |
| this.popup.close(); |
| } |
| }, |
| |
| onInputChange: function(){ |
| // summary: signal for changes in the input box |
| }, |
| |
| enable: function() { |
| // summary: enable this widget to accept user input |
| this.inputNode.disabled = false; |
| dojo.widget.DropdownContainer.superclass.enable.apply(this, arguments); |
| }, |
| |
| disable: function() { |
| // summary: lock this widget so that the user can't change the value |
| this.inputNode.disabled = true; |
| dojo.widget.DropdownContainer.superclass.disable.apply(this, arguments); |
| } |
| } |
| ); |
| |
| dojo.provide("dojo.widget.html.stabile"); |
| |
| dojo.widget.html.stabile = { |
| // summary: Maintain state of widgets when user hits back/forward button |
| |
| // Characters to quote in single-quoted regexprs |
| _sqQuotables: new RegExp("([\\\\'])", "g"), |
| |
| // Current depth. |
| _depth: 0, |
| |
| // Set to true when calling v.toString, to sniff for infinite |
| // recursion. |
| _recur: false, |
| |
| // Levels of nesting of Array and object displays. |
| // If when >= depth, no display or array or object internals. |
| depthLimit: 2 |
| }; |
| |
| //// PUBLIC METHODS |
| |
| dojo.widget.html.stabile.getState = function(id){ |
| // summary |
| // Get the state stored for the widget with the given ID, or undefined |
| // if none. |
| |
| dojo.widget.html.stabile.setup(); |
| return dojo.widget.html.stabile.widgetState[id]; |
| } |
| |
| dojo.widget.html.stabile.setState = function(id, state, isCommit){ |
| // summary |
| // Set the state stored for the widget with the given ID. If isCommit |
| // is true, commits all widget state to more stable storage. |
| |
| dojo.widget.html.stabile.setup(); |
| dojo.widget.html.stabile.widgetState[id] = state; |
| if(isCommit){ |
| dojo.widget.html.stabile.commit(dojo.widget.html.stabile.widgetState); |
| } |
| } |
| |
| dojo.widget.html.stabile.setup = function(){ |
| // summary |
| // Sets up widgetState: a hash keyed by widgetId, maps to an object |
| // or array writable with "describe". If there is data in the widget |
| // storage area, use it, otherwise initialize an empty object. |
| |
| if(!dojo.widget.html.stabile.widgetState){ |
| var text = dojo.widget.html.stabile._getStorage().value; |
| dojo.widget.html.stabile.widgetState = text ? dj_eval("("+text+")") : {}; |
| } |
| } |
| |
| dojo.widget.html.stabile.commit = function(state){ |
| // summary |
| // Commits all widget state to more stable storage, so if the user |
| // navigates away and returns, it can be restored. |
| |
| dojo.widget.html.stabile._getStorage().value = dojo.widget.html.stabile.description(state); |
| } |
| |
| dojo.widget.html.stabile.description = function(v, showAll){ |
| // summary |
| // Return a JSON "description string" for the given value. |
| // Supports only core JavaScript types with literals, plus Date, |
| // and cyclic structures are unsupported. |
| // showAll defaults to false -- if true, this becomes a simple symbolic |
| // object dumper, but you cannot "eval" the output. |
| |
| // Save and later restore dojo.widget.html.stabile._depth; |
| var depth = dojo.widget.html.stabile._depth; |
| |
| var describeThis = function() { |
| return this.description(this, true); |
| } |
| |
| try { |
| |
| if(v===void(0)){ |
| return "undefined"; |
| } |
| if(v===null){ |
| return "null"; |
| } |
| if(typeof(v)=="boolean" || typeof(v)=="number" |
| || v instanceof Boolean || v instanceof Number){ |
| return v.toString(); |
| } |
| |
| if(typeof(v)=="string" || v instanceof String){ |
| // Quote strings and their contents as required. |
| // Replacing by $& fails in IE 5.0 |
| var v1 = v.replace(dojo.widget.html.stabile._sqQuotables, "\\$1"); |
| v1 = v1.replace(/\n/g, "\\n"); |
| v1 = v1.replace(/\r/g, "\\r"); |
| // Any other important special cases? |
| return "'"+v1+"'"; |
| } |
| |
| if(v instanceof Date){ |
| // Create a data constructor. |
| return "new Date("+d.getFullYear+","+d.getMonth()+","+d.getDate()+")"; |
| } |
| |
| var d; |
| if(v instanceof Array || v.push){ |
| // "push" test needed for KHTML/Safari, don't know why -cp |
| |
| if(depth>=dojo.widget.html.stabile.depthLimit) |
| return "[ ... ]"; |
| |
| d = "["; |
| var first = true; |
| dojo.widget.html.stabile._depth++; |
| for(var i=0; i<v.length; i++){ |
| // Skip functions and undefined values |
| // if(v[i]==undef || typeof(v[i])=="function") |
| // continue; |
| if(first){ |
| first = false; |
| }else{ |
| d += ","; |
| } |
| d+=arguments.callee(v[i], showAll); |
| } |
| return d+"]"; |
| } |
| |
| if(v.constructor==Object |
| || v.toString==describeThis){ |
| if(depth>=dojo.widget.html.stabile.depthLimit) |
| return "{ ... }"; |
| |
| // Instanceof Hash is good, or if we just use Objects, |
| // we can say v.constructor==Object. |
| // IE (5?) lacks hasOwnProperty, but perhaps objects do not always |
| // have prototypes?? |
| if(typeof(v.hasOwnProperty)!="function" && v.prototype){ |
| throw new Error("description: "+v+" not supported by script engine"); |
| } |
| var first = true; |
| d = "{"; |
| dojo.widget.html.stabile._depth++; |
| for(var key in v){ |
| // Skip values that are functions or undefined. |
| if(v[key]==void(0) || typeof(v[key])=="function") |
| continue; |
| if(first){ |
| first = false; |
| }else{ |
| d += ", "; |
| } |
| var kd = key; |
| // If the key is not a legal identifier, use its description. |
| // For strings this will quote the stirng. |
| if(!kd.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)){ |
| kd = arguments.callee(key, showAll); |
| } |
| d += kd+": "+arguments.callee(v[key], showAll); |
| } |
| return d+"}"; |
| } |
| |
| if(showAll){ |
| if(dojo.widget.html.stabile._recur){ |
| // Save the original definitions of toString; |
| var objectToString = Object.prototype.toString; |
| return objectToString.apply(v, []); |
| }else{ |
| dojo.widget.html.stabile._recur = true; |
| return v.toString(); |
| } |
| }else{ |
| // log("Description? "+v.toString()+", "+typeof(v)); |
| throw new Error("Unknown type: "+v); |
| return "'unknown'"; |
| } |
| |
| } finally { |
| // Always restore the global current depth. |
| dojo.widget.html.stabile._depth = depth; |
| } |
| |
| } |
| |
| |
| |
| //// PRIVATE TO MODULE |
| |
| dojo.widget.html.stabile._getStorage = function(){ |
| // summary |
| // Gets an object (form field) with a read/write "value" property. |
| |
| if (dojo.widget.html.stabile.dataField) { |
| return dojo.widget.html.stabile.dataField; |
| } |
| var form = document.forms._dojo_form; |
| return dojo.widget.html.stabile.dataField = form ? form.stabile : {value: ""}; |
| } |
| |
| |
| dojo.provide("dojo.widget.Dialog"); |
| |
| |
| |
| dojo.require("dojo.event.*"); |
| |
| |
| dojo.require("dojo.html.display"); |
| |
| |
| dojo.declare( |
| "dojo.widget.ModalDialogBase", |
| null, |
| { |
| // summary |
| // Mixin for widgets implementing a modal dialog |
| |
| isContainer: true, |
| |
| // focusElement: String |
| // provide a focusable element or element id if you need to |
| // work around FF's tendency to send focus into outer space on hide |
| focusElement: "", |
| |
| // bgColor: String |
| // color of viewport when displaying a dialog |
| bgColor: "black", |
| |
| // bgOpacity: Number |
| // opacity (0~1) of viewport color (see bgColor attribute) |
| bgOpacity: 0.4, |
| |
| // followScroll: Boolean |
| // if true, readjusts the dialog (and dialog background) when the user moves the scrollbar |
| followScroll: true, |
| |
| // closeOnBackgroundClick: Boolean |
| // clicking anywhere on the background will close the dialog |
| closeOnBackgroundClick: false, |
| |
| trapTabs: function(/*Event*/ e){ |
| // summary |
| // callback on focus |
| if(e.target == this.tabStartOuter) { |
| if(this._fromTrap) { |
| this.tabStart.focus(); |
| this._fromTrap = false; |
| } else { |
| this._fromTrap = true; |
| this.tabEnd.focus(); |
| } |
| } else if (e.target == this.tabStart) { |
| if(this._fromTrap) { |
| this._fromTrap = false; |
| } else { |
| this._fromTrap = true; |
| this.tabEnd.focus(); |
| } |
| } else if(e.target == this.tabEndOuter) { |
| if(this._fromTrap) { |
| this.tabEnd.focus(); |
| this._fromTrap = false; |
| } else { |
| this._fromTrap = true; |
| this.tabStart.focus(); |
| } |
| } else if(e.target == this.tabEnd) { |
| if(this._fromTrap) { |
| this._fromTrap = false; |
| } else { |
| this._fromTrap = true; |
| this.tabStart.focus(); |
| } |
| } |
| }, |
| |
| clearTrap: function(/*Event*/ e) { |
| // summary |
| // callback on blur |
| var _this = this; |
| setTimeout(function() { |
| _this._fromTrap = false; |
| }, 100); |
| }, |
| |
| postCreate: function() { |
| // summary |
| // if the target mixin class already defined postCreate, |
| // dojo.widget.ModalDialogBase.prototype.postCreate.call(this) |
| // should be called in its postCreate() |
| with(this.domNode.style){ |
| position = "absolute"; |
| zIndex = 999; |
| display = "none"; |
| overflow = "visible"; |
| } |
| var b = dojo.body(); |
| b.appendChild(this.domNode); |
| |
| // make background (which sits behind the dialog but above the normal text) |
| this.bg = document.createElement("div"); |
| this.bg.className = "dialogUnderlay"; |
| with(this.bg.style){ |
| position = "absolute"; |
| left = top = "0px"; |
| zIndex = 998; |
| display = "none"; |
| } |
| b.appendChild(this.bg); |
| this.setBackgroundColor(this.bgColor); |
| |
| this.bgIframe = new dojo.html.BackgroundIframe(); |
| if(this.bgIframe.iframe){ |
| with(this.bgIframe.iframe.style){ |
| position = "absolute"; |
| left = top = "0px"; |
| zIndex = 90; |
| display = "none"; |
| } |
| } |
| |
| if(this.closeOnBackgroundClick){ |
| dojo.event.kwConnect({srcObj: this.bg, srcFunc: "onclick", |
| adviceObj: this, adviceFunc: "onBackgroundClick", once: true}); |
| } |
| }, |
| |
| uninitialize: function(){ |
| this.bgIframe.remove(); |
| dojo.html.removeNode(this.bg, true); |
| }, |
| |
| setBackgroundColor: function(/*String*/ color) { |
| // summary |
| // changes background color specified by "bgColor" parameter |
| // usage: |
| // setBackgroundColor("black"); |
| // setBackgroundColor(0xff, 0xff, 0xff); |
| if(arguments.length >= 3) { |
| color = new dojo.gfx.color.Color(arguments[0], arguments[1], arguments[2]); |
| } else { |
| color = new dojo.gfx.color.Color(color); |
| } |
| this.bg.style.backgroundColor = color.toString(); |
| return this.bgColor = color; // String: the color |
| }, |
| |
| setBackgroundOpacity: function(/*Number*/ op) { |
| // summary |
| // changes background opacity set by "bgOpacity" parameter |
| if(arguments.length == 0) { op = this.bgOpacity; } |
| dojo.html.setOpacity(this.bg, op); |
| try { |
| this.bgOpacity = dojo.html.getOpacity(this.bg); |
| } catch (e) { |
| this.bgOpacity = op; |
| } |
| return this.bgOpacity; // Number: the opacity |
| }, |
| |
| _sizeBackground: function() { |
| if(this.bgOpacity > 0) { |
| |
| var viewport = dojo.html.getViewport(); |
| var h = viewport.height; |
| var w = viewport.width; |
| with(this.bg.style){ |
| width = w + "px"; |
| height = h + "px"; |
| } |
| var scroll_offset = dojo.html.getScroll().offset; |
| this.bg.style.top = scroll_offset.y + "px"; |
| this.bg.style.left = scroll_offset.x + "px"; |
| // process twice since the scroll bar may have been removed |
| // by the previous resizing |
| var viewport = dojo.html.getViewport(); |
| if (viewport.width != w) { this.bg.style.width = viewport.width + "px"; } |
| if (viewport.height != h) { this.bg.style.height = viewport.height + "px"; } |
| } |
| this.bgIframe.size(this.bg); |
| }, |
| |
| _showBackground: function() { |
| if(this.bgOpacity > 0) { |
| this.bg.style.display = "block"; |
| } |
| if(this.bgIframe.iframe){ |
| this.bgIframe.iframe.style.display = "block"; |
| } |
| }, |
| |
| placeModalDialog: function() { |
| // summary: position modal dialog in center of screen |
| |
| var scroll_offset = dojo.html.getScroll().offset; |
| var viewport_size = dojo.html.getViewport(); |
| |
| // find the size of the dialog (dialog needs to be showing to get the size) |
| var mb; |
| if(this.isShowing()){ |
| mb = dojo.html.getMarginBox(this.domNode); |
| }else{ |
| dojo.html.setVisibility(this.domNode, false); |
| dojo.html.show(this.domNode); |
| mb = dojo.html.getMarginBox(this.domNode); |
| dojo.html.hide(this.domNode); |
| dojo.html.setVisibility(this.domNode, true); |
| } |
| |
| var x = scroll_offset.x + (viewport_size.width - mb.width)/2; |
| var y = scroll_offset.y + (viewport_size.height - mb.height)/2; |
| with(this.domNode.style){ |
| left = x + "px"; |
| top = y + "px"; |
| } |
| }, |
| |
| _onKey: function(/*Event*/ evt){ |
| if (evt.key){ |
| // see if the key is for the dialog |
| var node = evt.target; |
| while (node != null){ |
| if (node == this.domNode){ |
| return; // yes, so just let it go |
| } |
| node = node.parentNode; |
| } |
| // this key is for the disabled document window |
| if (evt.key != evt.KEY_TAB){ // allow tabbing into the dialog for a11y |
| dojo.event.browser.stopEvent(evt); |
| // opera won't tab to a div |
| }else if (!dojo.render.html.opera){ |
| try { |
| this.tabStart.focus(); |
| } catch(e){} |
| } |
| } |
| }, |
| |
| showModalDialog: function() { |
| // summary |
| // call this function in show() of subclass before calling superclass.show() |
| if (this.followScroll && !this._scrollConnected){ |
| this._scrollConnected = true; |
| dojo.event.connect(window, "onscroll", this, "_onScroll"); |
| } |
| dojo.event.connect(document.documentElement, "onkey", this, "_onKey"); |
| |
| this.placeModalDialog(); |
| this.setBackgroundOpacity(); |
| this._sizeBackground(); |
| this._showBackground(); |
| this._fromTrap = true; |
| |
| // set timeout to allow the browser to render dialog |
| setTimeout(dojo.lang.hitch(this, function(){ |
| try{ |
| this.tabStart.focus(); |
| }catch(e){} |
| }), 50); |
| |
| }, |
| |
| hideModalDialog: function(){ |
| // summary |
| // call this function in hide() of subclass |
| |
| // workaround for FF focus going into outer space |
| if (this.focusElement) { |
| dojo.byId(this.focusElement).focus(); |
| dojo.byId(this.focusElement).blur(); |
| } |
| |
| this.bg.style.display = "none"; |
| this.bg.style.width = this.bg.style.height = "1px"; |
| if(this.bgIframe.iframe){ |
| this.bgIframe.iframe.style.display = "none"; |
| } |
| |
| dojo.event.disconnect(document.documentElement, "onkey", this, "_onKey"); |
| if (this._scrollConnected){ |
| this._scrollConnected = false; |
| dojo.event.disconnect(window, "onscroll", this, "_onScroll"); |
| } |
| }, |
| |
| _onScroll: function(){ |
| var scroll_offset = dojo.html.getScroll().offset; |
| this.bg.style.top = scroll_offset.y + "px"; |
| this.bg.style.left = scroll_offset.x + "px"; |
| this.placeModalDialog(); |
| }, |
| |
| checkSize: function() { |
| if(this.isShowing()){ |
| this._sizeBackground(); |
| this.placeModalDialog(); |
| this.onResized(); |
| } |
| }, |
| |
| onBackgroundClick: function(){ |
| // summary |
| // Callback on background click. |
| // Clicking anywhere on the background will close the dialog, but only |
| // if the dialog doesn't have an explicit close button, and only if |
| // the dialog doesn't have a blockDuration. |
| if(this.lifetime - this.timeRemaining >= this.blockDuration){ return; } |
| this.hide(); |
| } |
| }); |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.Dialog", |
| [dojo.widget.ContentPane, dojo.widget.ModalDialogBase], |
| { |
| // summary |
| // 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.) |
| |
| templateString:"<div id=\"${this.widgetId}\" class=\"dojoDialog\" dojoattachpoint=\"wrapper\">\n\t<span dojoattachpoint=\"tabStartOuter\" dojoonfocus=\"trapTabs\" dojoonblur=\"clearTrap\"\ttabindex=\"0\"></span>\n\t<span dojoattachpoint=\"tabStart\" dojoonfocus=\"trapTabs\" dojoonblur=\"clearTrap\" tabindex=\"0\"></span>\n\t<div dojoattachpoint=\"containerNode\" style=\"position: relative; z-index: 2;\"></div>\n\t<span dojoattachpoint=\"tabEnd\" dojoonfocus=\"trapTabs\" dojoonblur=\"clearTrap\" tabindex=\"0\"></span>\n\t<span dojoattachpoint=\"tabEndOuter\" dojoonfocus=\"trapTabs\" dojoonblur=\"clearTrap\" tabindex=\"0\"></span>\n</div>\n", |
| |
| // blockDuration: Integer |
| // number of seconds for which the user cannot dismiss the dialog |
| blockDuration: 0, |
| |
| // lifetime: Integer |
| // if set, this controls the number of seconds the dialog will be displayed before automatically disappearing |
| lifetime: 0, |
| |
| // closeNode: String |
| // Id of button or other dom node to click to close this dialog |
| closeNode: "", |
| |
| postMixInProperties: function(){ |
| dojo.widget.Dialog.superclass.postMixInProperties.apply(this, arguments); |
| if(this.closeNode){ |
| this.setCloseControl(this.closeNode); |
| } |
| }, |
| |
| postCreate: function(){ |
| dojo.widget.Dialog.superclass.postCreate.apply(this, arguments); |
| dojo.widget.ModalDialogBase.prototype.postCreate.apply(this, arguments); |
| }, |
| |
| show: function() { |
| if(this.lifetime){ |
| this.timeRemaining = this.lifetime; |
| if(this.timerNode){ |
| this.timerNode.innerHTML = Math.ceil(this.timeRemaining/1000); |
| } |
| if(this.blockDuration && this.closeNode){ |
| if(this.lifetime > this.blockDuration){ |
| this.closeNode.style.visibility = "hidden"; |
| }else{ |
| this.closeNode.style.display = "none"; |
| } |
| } |
| if (this.timer) { |
| clearInterval(this.timer); |
| } |
| this.timer = setInterval(dojo.lang.hitch(this, "_onTick"), 100); |
| } |
| |
| this.showModalDialog(); |
| dojo.widget.Dialog.superclass.show.call(this); |
| }, |
| |
| onLoad: function(){ |
| // when href is specified we need to reposition |
| // the dialog after the data is loaded |
| this.placeModalDialog(); |
| dojo.widget.Dialog.superclass.onLoad.call(this); |
| }, |
| |
| fillInTemplate: function(){ |
| // dojo.event.connect(this.domNode, "onclick", this, "killEvent"); |
| }, |
| |
| hide: function(){ |
| this.hideModalDialog(); |
| dojo.widget.Dialog.superclass.hide.call(this); |
| |
| if(this.timer){ |
| clearInterval(this.timer); |
| } |
| }, |
| |
| setTimerNode: function(node){ |
| // summary |
| // specify into which node to write the remaining # of seconds |
| // TODO: make this a parameter too |
| this.timerNode = node; |
| }, |
| |
| setCloseControl: function(/*String|DomNode*/ node) { |
| // summary |
| // Specify which node is the close button for this dialog. |
| // If no close node is specified then clicking anywhere on the screen will close the dialog. |
| this.closeNode = dojo.byId(node); |
| dojo.event.connect(this.closeNode, "onclick", this, "hide"); |
| }, |
| |
| setShowControl: function(/*String|DomNode*/ node) { |
| // summary |
| // when specified node is clicked, show this dialog |
| // TODO: make this a parameter too |
| node = dojo.byId(node); |
| dojo.event.connect(node, "onclick", this, "show"); |
| }, |
| |
| _onTick: function(){ |
| // summary |
| // callback every second that the timer clicks |
| if(this.timer){ |
| this.timeRemaining -= 100; |
| if(this.lifetime - this.timeRemaining >= this.blockDuration){ |
| // TODO: this block of code is executing over and over again, rather than just once |
| if(this.closeNode){ |
| this.closeNode.style.visibility = "visible"; |
| } |
| } |
| if(!this.timeRemaining){ |
| clearInterval(this.timer); |
| this.hide(); |
| }else if(this.timerNode){ |
| this.timerNode.innerHTML = Math.ceil(this.timeRemaining/1000); |
| } |
| } |
| } |
| } |
| ); |
| |
| dojo.provide("dojo.widget.ComboBox"); |
| |
| |
| dojo.require("dojo.event.*"); |
| |
| dojo.require("dojo.html.*"); |
| dojo.require("dojo.string"); |
| |
| |
| |
| dojo.declare( |
| "dojo.widget.incrementalComboBoxDataProvider", |
| null, |
| function(options){ |
| // summary: |
| // Reference implementation / interface for Combobox incremental data provider. |
| // This class takes a search string and returns values that match |
| // that search string. The filtering of values (to find values matching given |
| // search string) is done on the server. |
| // |
| // options: |
| // Structure containing {dataUrl: "foo.js?search={searchString}"} or similar data. |
| // dataUrl is a URL that is passed the search string a returns a JSON structure |
| // showing the matching values, like [ ["Alabama","AL"], ["Alaska","AK"], ["American Samoa","AS"] ] |
| |
| this.searchUrl = options.dataUrl; |
| |
| // TODO: cache doesn't work |
| this._cache = {}; |
| |
| this._inFlight = false; |
| this._lastRequest = null; |
| |
| // allowCache: Boolean |
| // Setting to use/not use cache for previously seen values |
| // TODO: caching doesn't work. |
| // TODO: read the setting for this value from the widget parameters |
| this.allowCache = false; |
| }, |
| { |
| _addToCache: function(/*String*/ keyword, /*Array*/ data){ |
| if(this.allowCache){ |
| this._cache[keyword] = data; |
| } |
| }, |
| |
| startSearch: function(/*String*/ searchStr, /*Function*/ callback){ |
| // summary: |
| // Start the search for patterns that match searchStr, and call |
| // specified callback functions with the results |
| // searchStr: |
| // The characters the user has typed into the <input>. |
| // callback: |
| // This function will be called with the result, as an |
| // array of label/value pairs (the value is used for the Select widget). Example: |
| // [ ["Alabama","AL"], ["Alaska","AK"], ["American Samoa","AS"] ] |
| |
| if(this._inFlight){ |
| // FIXME: implement backoff! |
| } |
| var tss = encodeURIComponent(searchStr); |
| var realUrl = dojo.string.substituteParams(this.searchUrl, {"searchString": tss}); |
| var _this = this; |
| var request = this._lastRequest = dojo.io.bind({ |
| url: realUrl, |
| method: "get", |
| mimetype: "text/json", |
| load: function(type, data, evt){ |
| _this._inFlight = false; |
| if(!dojo.lang.isArray(data)){ |
| var arrData = []; |
| for(var key in data){ |
| arrData.push([data[key], key]); |
| } |
| data = arrData; |
| } |
| _this._addToCache(searchStr, data); |
| if (request == _this._lastRequest){ |
| callback(data); |
| } |
| } |
| }); |
| this._inFlight = true; |
| } |
| } |
| ); |
| |
| dojo.declare( |
| "dojo.widget.basicComboBoxDataProvider", |
| null, |
| function(/*Object*/ options, /*DomNode*/ node){ |
| // summary: |
| // Reference implementation / interface for Combobox data provider. |
| // This class takes a search string and returns values that match |
| // that search string. All possible values for the combobox are downloaded |
| // on initialization, and then startSearch() runs locally, |
| // merely filting that downloaded list, to find values matching search string |
| // |
| // NOTE: this data provider is designed as a naive reference |
| // implementation, and as such it is written more for readability than |
| // speed. A deployable data provider would implement lookups, search |
| // caching (and invalidation), and a significantly less naive data |
| // structure for storage of items. |
| // |
| // options: Object |
| // Options object. Example: |
| // { |
| // dataUrl: String (URL to query to get list of possible drop down values), |
| // setAllValues: Function (callback for setting initially selected value) |
| // } |
| // The return format for dataURL is (for example) |
| // [ ["Alabama","AL"], ["Alaska","AK"], ["American Samoa","AS"] ... ] |
| // |
| // node: |
| // Pointer to the domNode in the original markup. |
| // This is needed in the case when the list of values is embedded |
| // in the html like <select> <option>Alabama</option> <option>Arkansas</option> ... |
| // rather than specified as a URL. |
| |
| // _data: Array |
| // List of every possible value for the drop down list |
| // startSearch() simply searches this array and returns matching values. |
| this._data = []; |
| |
| // searchLimit: Integer |
| // Maximum number of results to return. |
| // TODO: need to read this value from the widget parameters |
| this.searchLimit = 30; |
| |
| // searchType: String |
| // Defines what values match the search string; see searchType parameter |
| // of ComboBox for details |
| // TODO: need to read this value from the widget parameters; the setting in ComboBox is being ignored. |
| this.searchType = "STARTSTRING"; |
| |
| // caseSensitive: Boolean |
| // Should search be case sensitive? |
| // TODO: this should be a parameter to combobox? |
| this.caseSensitive = false; |
| |
| if(!dj_undef("dataUrl", options) && !dojo.string.isBlank(options.dataUrl)){ |
| this._getData(options.dataUrl); |
| }else{ |
| // check to see if we can populate the list from <option> elements |
| if((node)&&(node.nodeName.toLowerCase() == "select")){ |
| // NOTE: we're not handling <optgroup> here yet |
| var opts = node.getElementsByTagName("option"); |
| var ol = opts.length; |
| var data = []; |
| for(var x=0; x<ol; x++){ |
| var text = opts[x].textContent || opts[x].innerText || opts[x].innerHTML; |
| var keyValArr = [String(text), String(opts[x].value)]; |
| data.push(keyValArr); |
| if(opts[x].selected){ |
| options.setAllValues(keyValArr[0], keyValArr[1]); |
| } |
| } |
| this.setData(data); |
| } |
| } |
| }, |
| { |
| _getData: function(/*String*/ url){ |
| dojo.io.bind({ |
| url: url, |
| load: dojo.lang.hitch(this, function(type, data, evt){ |
| if(!dojo.lang.isArray(data)){ |
| var arrData = []; |
| for(var key in data){ |
| arrData.push([data[key], key]); |
| } |
| data = arrData; |
| } |
| this.setData(data); |
| }), |
| mimetype: "text/json" |
| }); |
| }, |
| |
| startSearch: function(/*String*/ searchStr, /*Function*/ callback){ |
| // summary: |
| // Start the search for patterns that match searchStr. |
| // searchStr: |
| // The characters the user has typed into the <input>. |
| // callback: |
| // This function will be called with the result, as an |
| // array of label/value pairs (the value is used for the Select widget). Example: |
| // [ ["Alabama","AL"], ["Alaska","AK"], ["American Samoa","AS"] ] |
| |
| // FIXME: need to add timeout handling here!! |
| this._performSearch(searchStr, callback); |
| }, |
| |
| _performSearch: function(/*String*/ searchStr, /*Function*/ callback){ |
| // |
| // NOTE: this search is LINEAR, which means that it exhibits perhaps |
| // the worst possible speed characteristics of any search type. It's |
| // written this way to outline the responsibilities and interfaces for |
| // a search. |
| // |
| var st = this.searchType; |
| // FIXME: this is just an example search, which means that we implement |
| // only a linear search without any of the attendant (useful!) optimizations |
| var ret = []; |
| if(!this.caseSensitive){ |
| searchStr = searchStr.toLowerCase(); |
| } |
| for(var x=0; x<this._data.length; x++){ |
| if((this.searchLimit > 0)&&(ret.length >= this.searchLimit)){ |
| break; |
| } |
| // FIXME: we should avoid copies if possible! |
| var dataLabel = new String((!this.caseSensitive) ? this._data[x][0].toLowerCase() : this._data[x][0]); |
| if(dataLabel.length < searchStr.length){ |
| // this won't ever be a good search, will it? What if we start |
| // to support regex search? |
| continue; |
| } |
| |
| if(st == "STARTSTRING"){ |
| if(searchStr == dataLabel.substr(0, searchStr.length)){ |
| ret.push(this._data[x]); |
| } |
| }else if(st == "SUBSTRING"){ |
| // this one is a gimmie |
| if(dataLabel.indexOf(searchStr) >= 0){ |
| ret.push(this._data[x]); |
| } |
| }else if(st == "STARTWORD"){ |
| // do a substring search and then attempt to determine if the |
| // preceeding char was the beginning of the string or a |
| // whitespace char. |
| var idx = dataLabel.indexOf(searchStr); |
| if(idx == 0){ |
| // implicit match |
| ret.push(this._data[x]); |
| } |
| if(idx <= 0){ |
| // if we didn't match or implicily matched, march onward |
| continue; |
| } |
| // otherwise, we have to go figure out if the match was at the |
| // start of a word... |
| // this code is taken almost directy from nWidgets |
| var matches = false; |
| while(idx!=-1){ |
| // make sure the match either starts whole string, or |
| // follows a space, or follows some punctuation |
| if(" ,/(".indexOf(dataLabel.charAt(idx-1)) != -1){ |
| // FIXME: what about tab chars? |
| matches = true; break; |
| } |
| idx = dataLabel.indexOf(searchStr, idx+1); |
| } |
| if(!matches){ |
| continue; |
| }else{ |
| ret.push(this._data[x]); |
| } |
| } |
| } |
| callback(ret); |
| }, |
| |
| setData: function(/*Array*/ pdata){ |
| // summary: set (or reset) the data and initialize lookup structures |
| this._data = pdata; |
| } |
| } |
| ); |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.ComboBox", |
| dojo.widget.HtmlWidget, |
| { |
| // summary: |
| // Auto-completing text box, and base class for Select widget. |
| // |
| // The drop down box's values are populated from an class called |
| // a data provider, which returns a list of values based on the characters |
| // that the user has typed into the input box. |
| // |
| // Some of the options to the ComboBox are actually arguments to the data |
| // provider. |
| |
| // forceValidOption: Boolean |
| // If true, only allow selection of strings in drop down list. |
| // If false, user can select a value from the drop down, or just type in |
| // any random value. |
| forceValidOption: false, |
| |
| // searchType: String |
| // Argument to data provider. |
| // Specifies rule for matching typed in string w/list of available auto-completions. |
| // startString - look for auto-completions that start w/the specified string. |
| // subString - look for auto-completions containing the typed in string. |
| // startWord - look for auto-completions where any word starts w/the typed in string. |
| searchType: "stringstart", |
| |
| // dataProvider: Object |
| // (Read only) reference to data provider object created for this combobox |
| // according to "dataProviderClass" argument. |
| dataProvider: null, |
| |
| // autoComplete: Boolean |
| // If you type in a partial string, and then tab out of the <input> box, |
| // automatically copy the first entry displayed in the drop down list to |
| // the <input> field |
| autoComplete: true, |
| |
| // searchDelay: Integer |
| // Delay in milliseconds between when user types something and we start |
| // searching based on that value |
| searchDelay: 100, |
| |
| // dataUrl: String |
| // URL argument passed to data provider object (class name specified in "dataProviderClass") |
| // An example of the URL format for the default data provider is |
| // "remoteComboBoxData.js?search=%{searchString}" |
| dataUrl: "", |
| |
| // fadeTime: Integer |
| // Milliseconds duration of fadeout for drop down box |
| fadeTime: 200, |
| |
| // maxListLength: Integer |
| // Limits list to X visible rows, scroll on rest |
| maxListLength: 8, |
| |
| // mode: String |
| // Mode must be specified unless dataProviderClass is specified. |
| // "local" to inline search string, "remote" for JSON-returning live search |
| // or "html" for dumber live search. |
| mode: "local", |
| |
| // selectedResult: Array |
| // (Read only) array specifying the value/label that the user selected |
| selectedResult: null, |
| |
| // dataProviderClass: String |
| // Name of data provider class (code that maps a search string to a list of values) |
| // The class must match the interface demonstrated by dojo.widget.incrementalComboBoxDataProvider |
| dataProviderClass: "", |
| |
| // buttonSrc: URI |
| // URI for the down arrow icon to the right of the input box. |
| buttonSrc: dojo.uri.moduleUri("dojo.widget", "templates/images/combo_box_arrow.png"), |
| |
| // dropdownToggle: String |
| // Animation effect for showing/displaying drop down box |
| dropdownToggle: "fade", |
| |
| templateString:"<span _=\"whitespace and CR's between tags adds in FF\"\n\tclass=\"dojoComboBoxOuter\"\n\t><input style=\"display:none\" tabindex=\"-1\" name=\"\" value=\"\" \n\t\tdojoAttachPoint=\"comboBoxValue\"\n\t><input style=\"display:none\" tabindex=\"-1\" name=\"\" value=\"\" \n\t\tdojoAttachPoint=\"comboBoxSelectionValue\"\n\t><input type=\"text\" autocomplete=\"off\" class=\"dojoComboBox\"\n\t\tdojoAttachEvent=\"key:_handleKeyEvents; keyUp: onKeyUp; compositionEnd; onResize;\"\n\t\tdojoAttachPoint=\"textInputNode\"\n\t><img hspace=\"0\"\n\t\tvspace=\"0\"\n\t\tclass=\"dojoComboBox\"\n\t\tdojoAttachPoint=\"downArrowNode\"\n\t\tdojoAttachEvent=\"onMouseUp: handleArrowClick; onResize;\"\n\t\tsrc=\"${this.buttonSrc}\"\n></span>\n", |
| templateCssString:".dojoComboBoxOuter {\n\tborder: 0px !important;\n\tmargin: 0px !important;\n\tpadding: 0px !important;\n\tbackground: transparent !important;\n\twhite-space: nowrap !important;\n}\n\n.dojoComboBox {\n\tborder: 1px inset #afafaf;\n\tmargin: 0px;\n\tpadding: 0px;\n\tvertical-align: middle !important;\n\tfloat: none !important;\n\tposition: static !important;\n\tdisplay: inline !important;\n}\n\n/* the input box */\ninput.dojoComboBox {\n\tborder-right-width: 0px !important; \n\tmargin-right: 0px !important;\n\tpadding-right: 0px !important;\n}\n\n/* the down arrow */\nimg.dojoComboBox {\n\tborder-left-width: 0px !important;\n\tpadding-left: 0px !important;\n\tmargin-left: 0px !important;\n}\n\n/* IE vertical-alignment calculations can be off by +-1 but these margins are collapsed away */\n.dj_ie img.dojoComboBox {\n\tmargin-top: 1px; \n\tmargin-bottom: 1px; \n}\n\n/* the drop down */\n.dojoComboBoxOptions {\n\tfont-family: Verdana, Helvetica, Garamond, sans-serif;\n\t/* font-size: 0.7em; */\n\tbackground-color: white;\n\tborder: 1px solid #afafaf;\n\tposition: absolute;\n\tz-index: 1000; \n\toverflow: auto;\n\tcursor: default;\n}\n\n.dojoComboBoxItem {\n\tpadding-left: 2px;\n\tpadding-top: 2px;\n\tmargin: 0px;\n}\n\n.dojoComboBoxItemEven {\n\tbackground-color: #f4f4f4;\n}\n\n.dojoComboBoxItemOdd {\n\tbackground-color: white;\n}\n\n.dojoComboBoxItemHighlight {\n\tbackground-color: #63709A;\n\tcolor: white;\n}\n",templateCssPath: dojo.uri.moduleUri("dojo.widget", "templates/ComboBox.css"), |
| |
| setValue: function(/*String*/ value){ |
| // summary: Sets the value of the combobox |
| this.comboBoxValue.value = value; |
| if (this.textInputNode.value != value){ // prevent mucking up of selection |
| this.textInputNode.value = value; |
| // only change state and value if a new value is set |
| dojo.widget.html.stabile.setState(this.widgetId, this.getState(), true); |
| this.onValueChanged(value); |
| } |
| }, |
| |
| onValueChanged: function(/*String*/ value){ |
| // summary: callback when value changes, for user to attach to |
| }, |
| |
| getValue: function(){ |
| // summary: Rerturns combo box value |
| return this.comboBoxValue.value; |
| }, |
| |
| getState: function(){ |
| // summary: |
| // Used for saving state of ComboBox when navigates to a new |
| // page, in case they then hit the browser's "Back" button. |
| return {value: this.getValue()}; |
| }, |
| |
| setState: function(/*Object*/ state){ |
| // summary: |
| // Used for restoring state of ComboBox when has navigated to a new |
| // page but then hits browser's "Back" button. |
| this.setValue(state.value); |
| }, |
| |
| enable:function(){ |
| this.disabled=false; |
| this.textInputNode.removeAttribute("disabled"); |
| }, |
| |
| disable: function(){ |
| this.disabled = true; |
| this.textInputNode.setAttribute("disabled",true); |
| }, |
| |
| _getCaretPos: function(/*DomNode*/ element){ |
| // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 |
| if(dojo.lang.isNumber(element.selectionStart)){ |
| // FIXME: this is totally borked on Moz < 1.3. Any recourse? |
| return element.selectionStart; |
| }else if(dojo.render.html.ie){ |
| // in the case of a mouse click in a popup being handled, |
| // then the document.selection is not the textarea, but the popup |
| // var r = document.selection.createRange(); |
| // hack to get IE 6 to play nice. What a POS browser. |
| var tr = document.selection.createRange().duplicate(); |
| var ntr = element.createTextRange(); |
| tr.move("character",0); |
| ntr.move("character",0); |
| try { |
| // If control doesnt have focus, you get an exception. |
| // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes). |
| // There appears to be no workaround for this - googled for quite a while. |
| ntr.setEndPoint("EndToEnd", tr); |
| return String(ntr.text).replace(/\r/g,"").length; |
| } catch (e){ |
| return 0; // If focus has shifted, 0 is fine for caret pos. |
| } |
| |
| } |
| }, |
| |
| _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ |
| location = parseInt(location); |
| this._setSelectedRange(element, location, location); |
| }, |
| |
| _setSelectedRange: function(/*DomNode*/ element, /*Number*/ start, /*Number*/ end){ |
| if(!end){ end = element.value.length; } // NOTE: Strange - should be able to put caret at start of text? |
| // Mozilla |
| // parts borrowed from http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130 |
| if(element.setSelectionRange){ |
| element.focus(); |
| element.setSelectionRange(start, end); |
| }else if(element.createTextRange){ // IE |
| var range = element.createTextRange(); |
| with(range){ |
| collapse(true); |
| moveEnd('character', end); |
| moveStart('character', start); |
| select(); |
| } |
| }else{ //otherwise try the event-creation hack (our own invention) |
| // do we need these? |
| element.value = element.value; |
| element.blur(); |
| element.focus(); |
| // figure out how far back to go |
| var dist = parseInt(element.value.length)-end; |
| var tchar = String.fromCharCode(37); |
| var tcc = tchar.charCodeAt(0); |
| for(var x = 0; x < dist; x++){ |
| var te = document.createEvent("KeyEvents"); |
| te.initKeyEvent("keypress", true, true, null, false, false, false, false, tcc, tcc); |
| element.dispatchEvent(te); |
| } |
| } |
| }, |
| |
| _handleKeyEvents: function(/*Event*/ evt){ |
| // summary: handles keyboard events |
| if(evt.ctrlKey || evt.altKey || !evt.key){ return; } |
| |
| // reset these |
| this._prev_key_backspace = false; |
| this._prev_key_esc = false; |
| |
| var k = dojo.event.browser.keys; |
| var doSearch = true; |
| |
| switch(evt.key){ |
| case k.KEY_DOWN_ARROW: |
| if(!this.popupWidget.isShowingNow){ |
| this._startSearchFromInput(); |
| } |
| this._highlightNextOption(); |
| dojo.event.browser.stopEvent(evt); |
| return; |
| case k.KEY_UP_ARROW: |
| this._highlightPrevOption(); |
| dojo.event.browser.stopEvent(evt); |
| return; |
| case k.KEY_TAB: |
| // using linux alike tab for autocomplete |
| if(!this.autoComplete && this.popupWidget.isShowingNow && this._highlighted_option){ |
| dojo.event.browser.stopEvent(evt); |
| this._selectOption({ 'target': this._highlighted_option, 'noHide': false}); |
| |
| // put caret last |
| this._setSelectedRange(this.textInputNode, this.textInputNode.value.length, null); |
| }else{ |
| this._selectOption(); |
| return; |
| } |
| break; |
| case k.KEY_ENTER: |
| // prevent submitting form if we press enter with list open |
| if(this.popupWidget.isShowingNow){ |
| dojo.event.browser.stopEvent(evt); |
| } |
| if(this.autoComplete){ |
| this._selectOption(); |
| return; |
| } |
| // fallthrough |
| case " ": |
| if(this.popupWidget.isShowingNow && this._highlighted_option){ |
| dojo.event.browser.stopEvent(evt); |
| this._selectOption(); |
| this._hideResultList(); |
| return; |
| } |
| break; |
| case k.KEY_ESCAPE: |
| this._hideResultList(); |
| this._prev_key_esc = true; |
| return; |
| case k.KEY_BACKSPACE: |
| this._prev_key_backspace = true; |
| if(!this.textInputNode.value.length){ |
| this.setAllValues("", ""); |
| this._hideResultList(); |
| doSearch = false; |
| } |
| break; |
| case k.KEY_RIGHT_ARROW: // fall through |
| case k.KEY_LEFT_ARROW: // fall through |
| doSearch = false; |
| break; |
| default:// non char keys (F1-F12 etc..) shouldn't open list |
| if(evt.charCode==0){ |
| doSearch = false; |
| } |
| } |
| |
| if(this.searchTimer){ |
| clearTimeout(this.searchTimer); |
| } |
| if(doSearch){ |
| // if we have gotten this far we dont want to keep our highlight |
| this._blurOptionNode(); |
| |
| // need to wait a tad before start search so that the event bubbles through DOM and we have value visible |
| this.searchTimer = setTimeout(dojo.lang.hitch(this, this._startSearchFromInput), this.searchDelay); |
| } |
| }, |
| |
| compositionEnd: function(/*Event*/ evt){ |
| // summary: When inputting characters using an input method, such as Asian |
| // languages, it will generate this event instead of onKeyDown event |
| evt.key = evt.keyCode; |
| this._handleKeyEvents(evt); |
| }, |
| |
| onKeyUp: function(/*Event*/ evt){ |
| // summary: callback on key up event |
| this.setValue(this.textInputNode.value); |
| }, |
| |
| setSelectedValue: function(/*String*/ value){ |
| // summary: |
| // This sets a hidden value associated w/the displayed value. |
| // The hidden value (and this function) shouldn't be used; if |
| // you need a hidden value then use Select widget instead of ComboBox. |
| // TODO: remove? |
| // FIXME, not sure what to do here! |
| this.comboBoxSelectionValue.value = value; |
| }, |
| |
| setAllValues: function(/*String*/ value1, /*String*/ value2){ |
| // summary: |
| // This sets the displayed value and hidden value. |
| // The hidden value (and this function) shouldn't be used; if |
| // you need a hidden value then use Select widget instead of ComboBox. |
| this.setSelectedValue(value2); |
| this.setValue(value1); |
| }, |
| |
| _focusOptionNode: function(/*DomNode*/ node){ |
| // summary: does the actual highlight |
| if(this._highlighted_option != node){ |
| this._blurOptionNode(); |
| this._highlighted_option = node; |
| dojo.html.addClass(this._highlighted_option, "dojoComboBoxItemHighlight"); |
| } |
| }, |
| |
| _blurOptionNode: function(){ |
| // sumary: removes highlight on highlighted |
| if(this._highlighted_option){ |
| dojo.html.removeClass(this._highlighted_option, "dojoComboBoxItemHighlight"); |
| this._highlighted_option = null; |
| } |
| }, |
| |
| _highlightNextOption: function(){ |
| if((!this._highlighted_option) || !this._highlighted_option.parentNode){ |
| this._focusOptionNode(this.optionsListNode.firstChild); |
| }else if(this._highlighted_option.nextSibling){ |
| this._focusOptionNode(this._highlighted_option.nextSibling); |
| } |
| dojo.html.scrollIntoView(this._highlighted_option); |
| }, |
| |
| _highlightPrevOption: function(){ |
| if(this._highlighted_option && this._highlighted_option.previousSibling){ |
| this._focusOptionNode(this._highlighted_option.previousSibling); |
| }else{ |
| this._highlighted_option = null; |
| this._hideResultList(); |
| return; |
| } |
| dojo.html.scrollIntoView(this._highlighted_option); |
| }, |
| |
| _itemMouseOver: function(/*Event*/ evt){ |
| if (evt.target === this.optionsListNode){ return; } |
| this._focusOptionNode(evt.target); |
| dojo.html.addClass(this._highlighted_option, "dojoComboBoxItemHighlight"); |
| }, |
| |
| _itemMouseOut: function(/*Event*/ evt){ |
| if (evt.target === this.optionsListNode){ return; } |
| this._blurOptionNode(); |
| }, |
| |
| onResize: function(){ |
| // summary: this function is called when the input area has changed size |
| var inputSize = dojo.html.getContentBox(this.textInputNode); |
| if( inputSize.height <= 0 ){ |
| // need more time to calculate size |
| dojo.lang.setTimeout(this, "onResize", 100); |
| return; |
| } |
| var buttonSize = { width: inputSize.height, height: inputSize.height}; |
| dojo.html.setContentBox(this.downArrowNode, buttonSize); |
| }, |
| |
| fillInTemplate: function(/*Object*/ args, /*Object*/ frag){ |
| // there's some browser specific CSS in ComboBox.css |
| dojo.html.applyBrowserClass(this.domNode); |
| |
| var source = this.getFragNodeRef(frag); |
| if (! this.name && source.name){ this.name = source.name; } |
| this.comboBoxValue.name = this.name; |
| this.comboBoxSelectionValue.name = this.name+"_selected"; |
| |
| /* different nodes get different parts of the style */ |
| dojo.html.copyStyle(this.domNode, source); |
| dojo.html.copyStyle(this.textInputNode, source); |
| dojo.html.copyStyle(this.downArrowNode, source); |
| with (this.downArrowNode.style){ // calculate these later |
| width = "0px"; |
| height = "0px"; |
| } |
| |
| // Use specified data provider class; if no class is specified |
| // then use comboboxDataProvider or incrmentalComboBoxDataProvider |
| // depending on setting of mode |
| var dpClass; |
| if(this.dataProviderClass){ |
| if(typeof this.dataProviderClass == "string"){ |
| dpClass = dojo.evalObjPath(this.dataProviderClass) |
| }else{ |
| dpClass = this.dataProviderClass; |
| } |
| }else{ |
| if(this.mode == "remote"){ |
| dpClass = dojo.widget.incrementalComboBoxDataProvider; |
| }else{ |
| dpClass = dojo.widget.basicComboBoxDataProvider; |
| } |
| } |
| this.dataProvider = new dpClass(this, this.getFragNodeRef(frag)); |
| |
| this.popupWidget = new dojo.widget.createWidget("PopupContainer", |
| {toggle: this.dropdownToggle, toggleDuration: this.toggleDuration}); |
| dojo.event.connect(this, 'destroy', this.popupWidget, 'destroy'); |
| this.optionsListNode = this.popupWidget.domNode; |
| this.domNode.appendChild(this.optionsListNode); |
| dojo.html.addClass(this.optionsListNode, 'dojoComboBoxOptions'); |
| dojo.event.connect(this.optionsListNode, 'onclick', this, '_selectOption'); |
| dojo.event.connect(this.optionsListNode, 'onmouseover', this, '_onMouseOver'); |
| dojo.event.connect(this.optionsListNode, 'onmouseout', this, '_onMouseOut'); |
| |
| // TODO: why does onmouseover and onmouseout connect to two separate handlers??? |
| dojo.event.connect(this.optionsListNode, "onmouseover", this, "_itemMouseOver"); |
| dojo.event.connect(this.optionsListNode, "onmouseout", this, "_itemMouseOut"); |
| }, |
| |
| _openResultList: function(/*Array*/ results){ |
| if (this.disabled){ |
| return; |
| } |
| this._clearResultList(); |
| if(!results.length){ |
| this._hideResultList(); |
| } |
| |
| if( (this.autoComplete)&& |
| (results.length)&& |
| (!this._prev_key_backspace)&& |
| (this.textInputNode.value.length > 0)){ |
| var cpos = this._getCaretPos(this.textInputNode); |
| // only try to extend if we added the last character at the end of the input |
| if((cpos+1) > this.textInputNode.value.length){ |
| // only add to input node as we would overwrite Capitalisation of chars |
| this.textInputNode.value += results[0][0].substr(cpos); |
| // build a new range that has the distance from the earlier |
| // caret position to the end of the first string selected |
| this._setSelectedRange(this.textInputNode, cpos, this.textInputNode.value.length); |
| } |
| } |
| |
| var even = true; |
| while(results.length){ |
| var tr = results.shift(); |
| if(tr){ |
| var td = document.createElement("div"); |
| td.appendChild(document.createTextNode(tr[0])); |
| td.setAttribute("resultName", tr[0]); |
| td.setAttribute("resultValue", tr[1]); |
| td.className = "dojoComboBoxItem "+((even) ? "dojoComboBoxItemEven" : "dojoComboBoxItemOdd"); |
| even = (!even); |
| this.optionsListNode.appendChild(td); |
| } |
| } |
| |
| // show our list (only if we have content, else nothing) |
| this._showResultList(); |
| }, |
| |
| _onFocusInput: function(){ |
| this._hasFocus = true; |
| }, |
| |
| _onBlurInput: function(){ |
| this._hasFocus = false; |
| this._handleBlurTimer(true, 500); |
| }, |
| |
| _handleBlurTimer: function(/*Boolean*/clear, /*Number*/ millisec){ |
| // summary: collect all blur timers issues here |
| if(this.blurTimer && (clear || millisec)){ |
| clearTimeout(this.blurTimer); |
| } |
| if(millisec){ // we ignore that zero is false and never sets as that never happens in this widget |
| this.blurTimer = dojo.lang.setTimeout(this, "_checkBlurred", millisec); |
| } |
| }, |
| |
| _onMouseOver: function(/*Event*/ evt){ |
| // summary: needed in IE and Safari as inputTextNode loses focus when scrolling optionslist |
| if(!this._mouseover_list){ |
| this._handleBlurTimer(true, 0); |
| this._mouseover_list = true; |
| } |
| }, |
| |
| _onMouseOut:function(/*Event*/ evt){ |
| // summary: needed in IE and Safari as inputTextNode loses focus when scrolling optionslist |
| var relTarget = evt.relatedTarget; |
| try { // fixes #1807 |
| if(!relTarget || relTarget.parentNode != this.optionsListNode){ |
| this._mouseover_list = false; |
| this._handleBlurTimer(true, 100); |
| this._tryFocus(); |
| } |
| }catch(e){} |
| }, |
| |
| _isInputEqualToResult: function(/*String*/ result){ |
| var input = this.textInputNode.value; |
| if(!this.dataProvider.caseSensitive){ |
| input = input.toLowerCase(); |
| result = result.toLowerCase(); |
| } |
| return (input == result); |
| }, |
| |
| _isValidOption: function(){ |
| var tgt = dojo.html.firstElement(this.optionsListNode); |
| var isValidOption = false; |
| while(!isValidOption && tgt){ |
| if(this._isInputEqualToResult(tgt.getAttribute("resultName"))){ |
| isValidOption = true; |
| }else{ |
| tgt = dojo.html.nextElement(tgt); |
| } |
| } |
| return isValidOption; |
| }, |
| |
| _checkBlurred: function(){ |
| if(!this._hasFocus && !this._mouseover_list){ |
| this._hideResultList(); |
| // clear the list if the user empties field and moves away. |
| if(!this.textInputNode.value.length){ |
| this.setAllValues("", ""); |
| return; |
| } |
| |
| var isValidOption = this._isValidOption(); |
| // enforce selection from option list |
| if(this.forceValidOption && !isValidOption){ |
| this.setAllValues("", ""); |
| return; |
| } |
| if(!isValidOption){// clear |
| this.setSelectedValue(""); |
| } |
| } |
| }, |
| |
| _selectOption: function(/*Event*/ evt){ |
| var tgt = null; |
| if(!evt){ |
| evt = { target: this._highlighted_option }; |
| } |
| |
| if(!dojo.html.isDescendantOf(evt.target, this.optionsListNode)){ |
| // handle autocompletion where the the user has hit ENTER or TAB |
| |
| // if the input is empty do nothing |
| if(!this.textInputNode.value.length){ |
| return; |
| } |
| tgt = dojo.html.firstElement(this.optionsListNode); |
| |
| // user has input value not in option list |
| if(!tgt || !this._isInputEqualToResult(tgt.getAttribute("resultName"))){ |
| return; |
| } |
| // otherwise the user has accepted the autocompleted value |
| }else{ |
| tgt = evt.target; |
| } |
| |
| while((tgt.nodeType!=1)||(!tgt.getAttribute("resultName"))){ |
| tgt = tgt.parentNode; |
| if(tgt === dojo.body()){ |
| return false; |
| } |
| } |
| |
| this.selectedResult = [tgt.getAttribute("resultName"), tgt.getAttribute("resultValue")]; |
| this.setAllValues(tgt.getAttribute("resultName"), tgt.getAttribute("resultValue")); |
| if(!evt.noHide){ |
| this._hideResultList(); |
| this._setSelectedRange(this.textInputNode, 0, null); |
| } |
| this._tryFocus(); |
| }, |
| |
| _clearResultList: function(){ |
| if(this.optionsListNode.innerHTML){ |
| this.optionsListNode.innerHTML = ""; // browser natively knows how to collect this memory |
| } |
| }, |
| |
| _hideResultList: function(){ |
| this.popupWidget.close(); |
| }, |
| |
| _showResultList: function(){ |
| // Our dear friend IE doesnt take max-height so we need to calculate that on our own every time |
| var childs = this.optionsListNode.childNodes; |
| if(childs.length){ |
| var visibleCount = Math.min(childs.length,this.maxListLength); |
| |
| with(this.optionsListNode.style) |
| { |
| display = ""; |
| if(visibleCount == childs.length){ |
| //no scrollbar is required, so unset height to let browser calcuate it, |
| //as in css, overflow is already set to auto |
| height = ""; |
| }else{ |
| //show it first to get the correct dojo.style.getOuterHeight(childs[0]) |
| //FIXME: shall we cache the height of the item? |
| height = visibleCount * dojo.html.getMarginBox(childs[0]).height +"px"; |
| } |
| width = (dojo.html.getMarginBox(this.domNode).width-2)+"px"; |
| } |
| this.popupWidget.open(this.domNode, this, this.downArrowNode); |
| }else{ |
| this._hideResultList(); |
| } |
| }, |
| |
| handleArrowClick: function(){ |
| // summary: callback when arrow is clicked |
| this._handleBlurTimer(true, 0); |
| this._tryFocus(); |
| if(this.popupWidget.isShowingNow){ |
| this._hideResultList(); |
| }else{ |
| // forces full population of results, if they click |
| // on the arrow it means they want to see more options |
| this._startSearch(""); |
| } |
| }, |
| |
| _tryFocus: function(){ |
| try { |
| this.textInputNode.focus(); |
| } catch (e){ |
| // element isn't focusable if disabled, or not visible etc - not easy to test for. |
| }; |
| }, |
| |
| _startSearchFromInput: function(){ |
| this._startSearch(this.textInputNode.value); |
| }, |
| |
| _startSearch: function(/*String*/ key){ |
| this.dataProvider.startSearch(key, dojo.lang.hitch(this, "_openResultList")); |
| }, |
| |
| postCreate: function(){ |
| this.onResize(); |
| |
| // TODO: add these attach events to template |
| dojo.event.connect(this.textInputNode, "onblur", this, "_onBlurInput"); |
| dojo.event.connect(this.textInputNode, "onfocus", this, "_onFocusInput"); |
| |
| if (this.disabled){ |
| this.disable(); |
| } |
| var s = dojo.widget.html.stabile.getState(this.widgetId); |
| if (s){ |
| this.setState(s); |
| } |
| } |
| } |
| ); |
| |
| dojo.provide("dojo.widget.Select"); |
| |
| |
| |
| |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.Select", |
| dojo.widget.ComboBox, |
| { |
| /* |
| * summary |
| * Enhanced version of HTML's <select> tag. |
| * |
| * 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) |
| */ |
| |
| // This value should not be changed by the user |
| forceValidOption: true, |
| |
| setValue: function(value) { |
| // summary |
| // Sets the value of the combobox. |
| // TODO: this doesn't work correctly when a URL is specified, because we can't |
| // set the label automatically (based on the specified value) |
| this.comboBoxValue.value = value; |
| dojo.widget.html.stabile.setState(this.widgetId, this.getState(), true); |
| this.onValueChanged(value); |
| }, |
| |
| setLabel: function(value){ |
| // summary |
| // FIXME, not sure what to do here! |
| // Users shouldn't call this function; they should be calling setValue() instead |
| this.comboBoxSelectionValue.value = value; |
| if (this.textInputNode.value != value) { // prevent mucking up of selection |
| this.textInputNode.value = value; |
| } |
| }, |
| |
| getLabel: function(){ |
| // summary: returns current label |
| return this.comboBoxSelectionValue.value; // String |
| }, |
| |
| getState: function() { |
| // summary: returns current value and label |
| return { |
| value: this.getValue(), |
| label: this.getLabel() |
| }; // Object |
| }, |
| |
| onKeyUp: function(/*Event*/ evt){ |
| // summary: internal function |
| this.setLabel(this.textInputNode.value); |
| }, |
| |
| setState: function(/*Object*/ state) { |
| // summary: internal function to set both value and label |
| this.setValue(state.value); |
| this.setLabel(state.label); |
| }, |
| |
| setAllValues: function(/*String*/ value1, /*String*/ value2){ |
| // summary: internal function to set both value and label |
| this.setLabel(value1); |
| this.setValue(value2); |
| } |
| } |
| ); |
| |