/* 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 // => dojo:tag // => dojo:tag // => dojo:type // => prefix:type // => dojo:type // => 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 var p = tagName.indexOf(":"); if(p>=0){ return tagName; } // => 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; } // => dojo:tag if(tagName.substr(0,4) == "dojo"){ // FIXME: this assumes tag names are always lower case return "dojo:" + tagName.substring(4); } // => prefix:type // => dojo:type var djt = node.getAttribute("dojoType") || node.getAttribute("dojotype"); if(djt){ if (djt.indexOf(":")<0){ djt = "dojo:"+djt; } return djt.toLowerCase(); } // => dojo:type djt = node.getAttributeNS && node.getAttributeNS(dojo.dom.dojoml,"type"); if(djt){ return "dojo:" + djt.toLowerCase(); } // => 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(); } // => 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 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: //
...
// 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 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; xsi)){ 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 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 -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 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(/]*>\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= 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=0){ funcs = dojo.lang.map(thisFunc.split(";"), trim); } for(var z=0; z0)&&(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 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= 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; i0){ 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;inativeBoxType;--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 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 // If you wanted to insert a node into a DOM tree based on the mouse // position you might use the following code: //
	//	if (gravity(node, e) & gravity.NORTH) { [insert before]; }
	//	else { [insert after]; }
	//	
// // @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 ) 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]/i).test(txt.replace(/^\s+/))) { txt = "" + txt + "
"; tableType = "cell"; } else if((/^]/i).test(txt.replace(/^\s+/))) { txt = "" + txt + "
"; tableType = "row"; } else if((/^<(thead|tbody|tfoot)[\s\r\n>]/i).test(txt.replace(/^\s+/))) { txt = "" + txt + "
"; 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 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 .... 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 /************** ***********/ // 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); } } );