dojo.provide("dijit._Widget"); //>>excludeStart("dijitBaseExclude", kwArgs.customDijitBase == "true"); dojo.require( "dijit._base" ); //>>excludeEnd("dijitBaseExclude"); dojo.connect(dojo, "connect", function(/*Widget*/ widget, /*String*/ event){ if(widget && dojo.isFunction(widget._onConnect)){ widget._onConnect(event); } }); dijit._connectOnUseEventHandler = function(/*Event*/ event){}; (function(){ var _attrReg = {}; var getAttrReg = function(dc){ if(!_attrReg[dc]){ var r = []; var attrs; var proto = dojo.getObject(dc).prototype; for(var fxName in proto){ if(dojo.isFunction(proto[fxName]) && (attrs = fxName.match(/^_set([a-zA-Z]*)Attr$/)) && attrs[1]){ r.push(attrs[1].charAt(0).toLowerCase() + attrs[1].substr(1)); } } _attrReg[dc] = r; } return _attrReg[dc]||[]; } dojo.declare("dijit._Widget", null, { // summary: // Base class for all dijit widgets. // id: [const] 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. id: "", // lang: [const] String // Rarely used. Overrides the default Dojo locale used to render this widget, // as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute. // Value must be among the list of locales specified during by the Dojo bootstrap, // formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us). lang: "", // dir: [const] String // Unsupported by Dijit, but here for completeness. Dijit only supports setting text direction on the // entire document. // Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir) // attribute. Either left-to-right "ltr" or right-to-left "rtl". dir: "", // class: String // HTML class attribute "class": "", // style: String||Object // HTML style attributes as cssText string or name/value hash style: "", // title: String // HTML title attribute, used to specify the title of tabs, accordion panes, etc. title: "", // srcNodeRef: [readonly] DomNode // pointer to original dom node srcNodeRef: null, // domNode: [readonly] DomNode // This is our visible representation of the widget! Other DOM // Nodes may by assigned to other properties, usually through the // template system's dojoAttachPoint syntax, but the domNode // property is the canonical "top level" node in widget UI. domNode: null, // containerNode: [readonly] DomNode // Designates where children of the source dom node will be placed. // "Children" in this case refers to both dom nodes and widgets. // For example, for myWidget: // // |
// | here's a plain dom node // | and a widget // | and another plain dom node // |
// // containerNode would point to: // // | here's a plain dom node // | and a widget // | and another plain dom node // // In templated widgets, "containerNode" is set via a // dojoAttachPoint assignment. // // containerNode must be defined for any widget that accepts innerHTML // (like ContentPane or BorderContainer or even Button), and conversely // is null for widgets that don't, like TextBox. containerNode: null, // attributeMap: [protected] Object // attributeMap sets up a "binding" between attributes (aka properties) // of the widget and the widget's DOM. // Changes to widget attributes listed in attributeMap will be // reflected into the DOM. // // For example, calling attr('title', 'hello') // on a TitlePane will automatically cause the TitlePane's DOM to update // with the new title. // // attributeMap is a hash where the key is an attribute of the widget, // and the value reflects a binding to a: // // - DOM node attribute // | focus: {node: "focusNode", type: "attribute"} // Maps this.focus to this.focusNode.focus // // - DOM node innerHTML // | title: { node: "titleNode", type: "innerHTML" } // Maps this.title to this.titleNode.innerHTML // // - DOM node CSS class // | myClass: { node: "domNode", type: "class" } // Maps this.myClass to this.domNode.className // // If the value is an array, then each element in the array matches one of the // formats of the above list. // // There are also some shorthands for backwards compatibility: // - string --> { node: string, type: "attribute" }, for example: // | "focusNode" ---> { node: "focusNode", type: "attribute" } // - "" --> { node: "domNode", type: "attribute" } attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""}, // _deferredConnects: [protected] Object // attributeMap addendum for event handlers that should be connected only on first use _deferredConnects: { onClick: "", onDblClick: "", onKeyDown: "", onKeyPress: "", onKeyUp: "", onMouseMove: "", onMouseDown: "", onMouseOut: "", onMouseOver: "", onMouseLeave: "", onMouseEnter: "", onMouseUp: ""}, onClick: dijit._connectOnUseEventHandler, /*===== onClick: function(event){ // summary: // Connect to this function to receive notifications of mouse click events. // event: // mouse Event // tags: // callback }, =====*/ onDblClick: dijit._connectOnUseEventHandler, /*===== onDblClick: function(event){ // summary: // Connect to this function to receive notifications of mouse double click events. // event: // mouse Event // tags: // callback }, =====*/ onKeyDown: dijit._connectOnUseEventHandler, /*===== onKeyDown: function(event){ // summary: // Connect to this function to receive notifications of keys being pressed down. // event: // key Event // tags: // callback }, =====*/ onKeyPress: dijit._connectOnUseEventHandler, /*===== onKeyPress: function(event){ // summary: // Connect to this function to receive notifications of printable keys being typed. // event: // key Event // tags: // callback }, =====*/ onKeyUp: dijit._connectOnUseEventHandler, /*===== onKeyUp: function(event){ // summary: // Connect to this function to receive notifications of keys being released. // event: // key Event // tags: // callback }, =====*/ onMouseDown: dijit._connectOnUseEventHandler, /*===== onMouseDown: function(event){ // summary: // Connect to this function to receive notifications of when the mouse button is pressed down. // event: // mouse Event // tags: // callback }, =====*/ onMouseMove: dijit._connectOnUseEventHandler, /*===== onMouseMove: function(event){ // summary: // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget. // event: // mouse Event // tags: // callback }, =====*/ onMouseOut: dijit._connectOnUseEventHandler, /*===== onMouseOut: function(event){ // summary: // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget. // event: // mouse Event // tags: // callback }, =====*/ onMouseOver: dijit._connectOnUseEventHandler, /*===== onMouseOver: function(event){ // summary: // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget. // event: // mouse Event // tags: // callback }, =====*/ onMouseLeave: dijit._connectOnUseEventHandler, /*===== onMouseLeave: function(event){ // summary: // Connect to this function to receive notifications of when the mouse moves off of this widget. // event: // mouse Event // tags: // callback }, =====*/ onMouseEnter: dijit._connectOnUseEventHandler, /*===== onMouseEnter: function(event){ // summary: // Connect to this function to receive notifications of when the mouse moves onto this widget. // event: // mouse Event // tags: // callback }, =====*/ onMouseUp: dijit._connectOnUseEventHandler, /*===== onMouseUp: function(event){ // summary: // Connect to this function to receive notifications of when the mouse button is released. // event: // mouse Event // tags: // callback }, =====*/ // Constants used in templates // _blankGif: [protected] URL // Used by nodes in templates that really get there image via CSS background-image _blankGif: (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")), //////////// INITIALIZATION METHODS /////////////////////////////////////// postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){ // summary: // Kicks off widget instantiation. See create() for details. // tags: // private this.create(params, srcNodeRef); }, create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){ // summary: // Kick off the life-cycle of a widget // params: // Hash of initialization parameters for widget, including // scalar values (like title, duration etc.) and functions, // typically callbacks like onClick. // srcNodeRef: // If a srcNodeRef (dom node) is specified: // - use srcNodeRef.innerHTML as my contents // - if this is a behavioral widget then apply behavior // to that srcNodeRef // - otherwise, replace srcNodeRef with my generated DOM // tree // description: // To understand the process by which widgets are instantiated, it // is critical to understand what other methods create calls and // which of them you'll want to override. Of course, adventurous // developers could override create entirely, but this should // only be done as a last resort. // // Below is a list of the methods that are called, in the order // they are fired, along with notes about what they do and if/when // you should over-ride them in your widget: // // * postMixInProperties: // | * a stub function that you can over-ride to modify // variables that may have been naively assigned by // mixInProperties // * widget is added to manager object here // * buildRendering: // | * Subclasses use this method to handle all UI initialization // Sets this.domNode. Templated widgets do this automatically // and otherwise it just uses the source dom node. // * postCreate: // | * a stub function that you can over-ride to modify take // actions once the widget has been placed in the UI // tags: // private // store pointer to original dom tree this.srcNodeRef = dojo.byId(srcNodeRef); // For garbage collection. An array of handles returned by Widget.connect() // Each handle returned from Widget.connect() is an array of handles from dojo.connect() this._connects = []; // To avoid double-connects, remove entries from _deferredConnects // that have been setup manually by a subclass (ex, by dojoAttachEvent). // If a subclass has redefined a callback (ex: onClick) then assume it's being // connected to manually. this._deferredConnects = dojo.clone(this._deferredConnects); for(var attr in this.attributeMap){ delete this._deferredConnects[attr]; // can't be in both attributeMap and _deferredConnects } for(attr in this._deferredConnects){ if(this[attr] !== dijit._connectOnUseEventHandler){ delete this._deferredConnects[attr]; // redefined, probably dojoAttachEvent exists } } //mixin our passed parameters if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; } if(params){ this.params = params; dojo.mixin(this,params); } this.postMixInProperties(); // generate an id for the widget if one wasn't specified // (be sure to do this before buildRendering() because that function might // expect the id to be there.) if(!this.id){ this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_")); } dijit.registry.add(this); this.buildRendering(); if(this.domNode){ // Copy attributes listed in attributeMap into the [newly created] DOM for the widget. this._applyAttributes(); var source = this.srcNodeRef; if(source && source.parentNode){ source.parentNode.replaceChild(this.domNode, source); } // If the developer has specified a handler as a widget parameter // (ex: new Button({onClick: ...}) // then naturally need to connect from dom node to that handler immediately, for(attr in this.params){ this._onConnect(attr); } } if(this.domNode){ this.domNode.setAttribute("widgetId", this.id); } this.postCreate(); // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC. if(this.srcNodeRef && !this.srcNodeRef.parentNode){ delete this.srcNodeRef; } this._created = true; }, _applyAttributes: function(){ // summary: // Step during widget creation to copy all widget attributes to the // DOM as per attributeMap and _setXXXAttr functions. // description: // Skips over blank/false attribute values, unless they were explicitly specified // as parameters to the widget, since those are the default anyway, // and setting tabIndex="" is different than not setting tabIndex at all. // // It processes the attributes in the attribute map first, and then // it goes through and processes the attributes for the _setXXXAttr // functions that have been specified // tags: // private var condAttrApply = function(attr, scope){ if( (scope.params && attr in scope.params) || scope[attr]){ scope.attr(attr, scope[attr]); } }; for(var attr in this.attributeMap){ condAttrApply(attr, this); } dojo.forEach(getAttrReg(this.declaredClass), function(a){ if(!(a in this.attributeMap)){ condAttrApply(a, this); } }, this); }, postMixInProperties: function(){ // 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. // tags: // protected }, buildRendering: function(){ // summary: // Construct the UI for this widget, setting this.domNode. Most // widgets will mixin `dijit._Templated`, which implements this // method. // tags: // protected this.domNode = this.srcNodeRef || dojo.create('div'); }, postCreate: function(){ // summary: // Called after a widget's dom has been setup // tags: // protected }, startup: function(){ // summary: // Called after a widget's children, and other widgets on the page, have been created. // Provides an opportunity to manipulate any children before they are displayed. // This is useful for composite widgets that need to control or layout sub-widgets. // Many layout widgets can use this as a wiring phase. this._started = true; }, //////////// DESTROY FUNCTIONS //////////////////////////////// destroyRecursive: function(/*Boolean?*/ preserveDom){ // summary: // Destroy this widget and it's descendants. This is the generic // "destructor" function that all widget users should call to // cleanly discard with a widget. Once a widget is destroyed, it's // removed from the manager object. // preserveDom: // If true, this method will leave the original Dom structure // alone of descendant Widgets. Note: This will NOT work with // dijit._Templated widgets. this.destroyDescendants(preserveDom); this.destroy(preserveDom); }, destroy: function(/*Boolean*/ preserveDom){ // summary: // Destroy this widget, but not its descendants. // Will, however, destroy internal widgets such as those used within a template. // preserveDom: Boolean // If true, this method will leave the original Dom structure alone. // Note: This will not yet work with _Templated widgets this.uninitialize(); dojo.forEach(this._connects, function(array){ dojo.forEach(array, dojo.disconnect); }); // destroy widgets created as part of template, etc. dojo.forEach(this._supportingWidgets||[], function(w){ if(w.destroy){ w.destroy(); } }); this.destroyRendering(preserveDom); dijit.registry.remove(this.id); }, destroyRendering: function(/*Boolean?*/ preserveDom){ // summary: // Destroys the DOM nodes associated with this widget // preserveDom: // If true, this method will leave the original Dom structure alone // during tear-down. Note: this will not work with _Templated // widgets yet. // tags: // protected if(this.bgIframe){ this.bgIframe.destroy(preserveDom); delete this.bgIframe; } if(this.domNode){ if(preserveDom){ dojo.removeAttr(this.domNode, "widgetId"); }else{ dojo.destroy(this.domNode); } delete this.domNode; } if(this.srcNodeRef){ if(!preserveDom){ dojo.destroy(this.srcNodeRef); } delete this.srcNodeRef; } }, destroyDescendants: function(/*Boolean?*/ preserveDom){ // summary: // Recursively destroy the children of this widget and their // descendants. // preserveDom: // If true, the preserveDom attribute is passed to all descendant // widget's .destroy() method. Not for use with _Templated // widgets. // get all direct descendants and destroy them recursively dojo.forEach(this.getChildren(), function(widget){ if(widget.destroyRecursive){ widget.destroyRecursive(preserveDom); } }); }, uninitialize: function(){ // summary: // Stub function. Override to implement custom widget tear-down // behavior. // tags: // protected return false; }, ////////////////// MISCELLANEOUS METHODS /////////////////// onFocus: function(){ // summary: // Called when the widget becomes "active" because // it or a widget inside of it either has focus, or has recently // been clicked. // tags: // callback }, onBlur: function(){ // summary: // Called when the widget stops being "active" because // focus moved to something outside of it, or the user // clicked somewhere outside of it, or the widget was // hidden. // tags: // callback }, _onFocus: function(e){ // summary: // This is where widgets do processing for when they are active, // such as changing CSS classes. See onFocus() for more details. // tags: // protected this.onFocus(); }, _onBlur: function(){ // summary: // This is where widgets do processing for when they stop being active, // such as changing CSS classes. See onBlur() for more details. // tags: // protected this.onBlur(); }, _onConnect: function(/*String*/ event){ // summary: // Called when someone connects to one of my handlers. // "Turn on" that handler if it isn't active yet. // // This is also called for every single initialization parameter // so need to do nothing for parameters like "id". // tags: // private if(event in this._deferredConnects){ var mapNode = this[this._deferredConnects[event]||'domNode']; this.connect(mapNode, event.toLowerCase(), event); delete this._deferredConnects[event]; } }, _setClassAttr: function(/*String*/ value){ // summary: // Custom setter for the CSS "class" attribute // tags: // protected var mapNode = this[this.attributeMap["class"]||'domNode']; dojo.removeClass(mapNode, this["class"]) this["class"] = value; dojo.addClass(mapNode, value); }, _setStyleAttr: function(/*String||Object*/ value){ // summary: // Sets the style attribut of the widget according to value, // which is either a hash like {height: "5px", width: "3px"} // or a plain string // description: // Determines which node to set the style on based on style setting // in attributeMap. // tags: // protected var mapNode = this[this.attributeMap["style"]||'domNode']; // Note: technically we should revert any style setting made in a previous call // to his method, but that's difficult to keep track of. if(dojo.isObject(value)){ dojo.style(mapNode, value); }else{ if(mapNode.style.cssText){ mapNode.style.cssText += "; " + value; }else{ mapNode.style.cssText = value; } } this["style"] = value; }, setAttribute: function(/*String*/ attr, /*anything*/ value){ // summary: // Deprecated. Use attr() instead. // tags: // deprecated dojo.deprecated(this.declaredClass+"::setAttribute() is deprecated. Use attr() instead.", "", "2.0"); this.attr(attr, value); }, _attrToDom: function(/*String*/ attr, /*String*/ value){ // summary: // Reflect a widget attribute (title, tabIndex, duration etc.) to // the widget DOM, as specified in attributeMap. // // description: // Also sets this["attr"] to the new value. // Note some attributes like "type" // cannot be processed this way as they are not mutable. // // tags: // private var commands = this.attributeMap[attr]; dojo.forEach( dojo.isArray(commands) ? commands : [commands], function(command){ // Get target node and what we are doing to that node var mapNode = this[command.node || command || "domNode"]; // DOM node var type = command.type || "attribute"; // class, innerHTML, or attribute switch(type){ case "attribute": if(dojo.isFunction(value)){ // functions execute in the context of the widget value = dojo.hitch(this, value); } if(/^on[A-Z][a-zA-Z]*$/.test(attr)){ // eg. onSubmit needs to be onsubmit attr = attr.toLowerCase(); } dojo.attr(mapNode, attr, value); break; case "innerHTML": mapNode.innerHTML = value; break; case "class": dojo.removeClass(mapNode, this[attr]); dojo.addClass(mapNode, value); break; } }, this); this[attr] = value; }, attr: function(/*String|Object*/name, /*Object?*/value){ // summary: // Set or get properties on a widget instance. // name: // The property to get or set. If an object is passed here and not // a string, its keys are used as names of attributes to be set // and the value of the object as values to set in the widget. // value: // Optional. If provided, attr() operates as a setter. If omitted, // the current value of the named property is returned. // description: // Get or set named properties on a widget. If no value is // provided, the current value of the attribute is returned, // potentially via a getter method. If a value is provided, then // the method acts as a setter, assigning the value to the name, // potentially calling any explicitly provided setters to handle // the operation. For instance, if the widget has properties "foo" // and "bar" and a method named "_setFooAttr", calling: // | myWidget.attr("foo", "Howdy!"); // would be equivalent to calling: // | widget._setFooAttr("Howdy!"); // while calling: // | myWidget.attr("bar", "Howdy!"); // would be the same as writing: // | widget.bar = "Howdy!"; // It also tries to copy the changes to the widget's DOM according // to settings in attributeMap (see description of `dijit._Widget.attributeMap` // for details) // For example, calling: // | myTitlePane.attr("title", "Howdy!"); // will do // | myTitlePane.title = "Howdy!"; // | myTitlePane.title.innerHTML = "Howdy!"; // It works for dom node attributes too. Calling // | widget.attr("disabled", true) // will set the disabled attribute on the widget's focusNode, // among other housekeeping for a change in disabled state. // open questions: // - how to handle build shortcut for attributes which want to map // into DOM attributes? // - what relationship should setAttribute()/attr() have to // layout() calls? var args = arguments.length; if(args == 1 && !dojo.isString(name)){ for(var x in name){ this.attr(x, name[x]); } return this; } var names = this._getAttrNames(name); if(args == 2){ // setter if(this[names.s]){ // use the explicit setter return this[names.s](value) || this; }else{ // if param is specified as DOM node attribute, copy it if(name in this.attributeMap){ this._attrToDom(name, value); } // FIXME: what about function assignments? Any way to connect() here? this[name] = value; } return this; }else{ // getter if(this[names.g]){ return this[names.g](); }else{ return this[name]; } } }, _attrPairNames: {}, // shared between all widgets _getAttrNames: function(name){ // summary: // Helper function for Widget.attr(). // Caches attribute name values so we don't do the string ops every time. // tags: // private var apn = this._attrPairNames; if(apn[name]){ return apn[name]; } var uc = name.charAt(0).toUpperCase() + name.substr(1); return apn[name] = { n: name+"Node", s: "_set"+uc+"Attr", g: "_get"+uc+"Attr" }; }, 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.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String }, getDescendants: function(){ // summary: // Returns all the widgets that contained by this, i.e., all widgets underneath this.containerNode. // This method should generally be avoided as it returns widgets declared in templates, which are // supposed to be internal/hidden, but it's left here for back-compat reasons. if(this.containerNode){ var list = dojo.query('[widgetId]', this.containerNode); return list.map(dijit.byNode); // Array }else{ return []; } }, getChildren: function(){ // summary: // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode. // Does not return nested widgets, nor widgets that are part of this widget's template. if(this.containerNode){ return dijit.findWidgets(this.containerNode); }else{ return []; } }, // nodesWithKeyClick: [private] String[] // List of nodes that correctly handle click events via native browser support, // and don't need dijit's help nodesWithKeyClick: ["input", "button"], connect: function( /*Object|null*/ obj, /*String|Function*/ event, /*String|Function*/ method){ // summary: // Connects specified obj/event to specified method of this object // and registers for disconnect() on widget destroy. // description: // Provide widget-specific analog to dojo.connect, except with the // implicit use of this widget as the target object. // This version of connect also provides a special "ondijitclick" // event which triggers on a click or space-up, enter-down in IE // or enter press in FF (since often can't cancel enter onkeydown // in FF) // example: // | var btn = new dijit.form.Button(); // | // when foo.bar() is called, call the listener we're going to // | // provide in the scope of btn // | btn.connect(foo, "bar", function(){ // | console.debug(this.toString()); // | }); // tags: // protected var d = dojo; var dc = dojo.connect; var handles =[]; if(event == "ondijitclick"){ // add key based click activation for unsupported nodes. if(!this.nodesWithKeyClick[obj.nodeName]){ var m = d.hitch(this, method); handles.push( dc(obj, "onkeydown", this, function(e){ if(!d.isFF && e.keyCode == d.keys.ENTER && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){ return m(e); }else if(e.keyCode == d.keys.SPACE){ // stop space down as it causes IE to scroll // the browser window d.stopEvent(e); } }), dc(obj, "onkeyup", this, function(e){ if(e.keyCode == d.keys.SPACE && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){ return m(e); } }) ); if(d.isFF){ handles.push( dc(obj, "onkeypress", this, function(e){ if(e.keyCode == d.keys.ENTER && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){ return m(e); } }) ); } } event = "onclick"; } handles.push(dc(obj, event, this, method)); // return handles for FormElement and ComboBox this._connects.push(handles); return handles; }, disconnect: function(/*Object*/ handles){ // summary: // Disconnects handle created by this.connect. // Also removes handle from this widget's list of connects // tags: // protected for(var i=0; i