You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1226 lines
45 KiB
1226 lines
45 KiB
define([ |
|
"require", // require.toUrl |
|
"dojo/_base/array", // array.forEach array.map |
|
"dojo/aspect", |
|
"dojo/_base/config", // config.blankGif |
|
"dojo/_base/connect", // connect.connect |
|
"dojo/_base/declare", // declare |
|
"dojo/dom", // dom.byId |
|
"dojo/dom-attr", // domAttr.set domAttr.remove |
|
"dojo/dom-class", // domClass.add domClass.replace |
|
"dojo/dom-construct", // domConstruct.destroy domConstruct.place |
|
"dojo/dom-geometry", // isBodyLtr |
|
"dojo/dom-style", // domStyle.set, domStyle.get |
|
"dojo/has", |
|
"dojo/_base/kernel", |
|
"dojo/_base/lang", // mixin(), isArray(), etc. |
|
"dojo/on", |
|
"dojo/ready", |
|
"dojo/Stateful", // Stateful |
|
"dojo/topic", |
|
"dojo/_base/window", // win.body() |
|
"./Destroyable", |
|
"dojo/has!dojo-bidi?./_BidiMixin", |
|
"./registry" // registry.getUniqueId(), registry.findWidgets() |
|
], function(require, array, aspect, config, connect, declare, |
|
dom, domAttr, domClass, domConstruct, domGeometry, domStyle, has, kernel, |
|
lang, on, ready, Stateful, topic, win, Destroyable, _BidiMixin, registry){ |
|
|
|
// module: |
|
// dijit/_WidgetBase |
|
|
|
// Flag to make dijit load modules the app didn't explicitly request, for backwards compatibility |
|
has.add("dijit-legacy-requires", !kernel.isAsync); |
|
|
|
// Flag to enable support for textdir attribute |
|
has.add("dojo-bidi", false); |
|
|
|
|
|
// For back-compat, remove in 2.0. |
|
if(has("dijit-legacy-requires")){ |
|
ready(0, function(){ |
|
var requires = ["dijit/_base/manager"]; |
|
require(requires); // use indirection so modules not rolled into a build |
|
}); |
|
} |
|
|
|
// Nested hash listing attributes for each tag, all strings in lowercase. |
|
// ex: {"div": {"style": true, "tabindex" true}, "form": { ... |
|
var tagAttrs = {}; |
|
|
|
function getAttrs(obj){ |
|
var ret = {}; |
|
for(var attr in obj){ |
|
ret[attr.toLowerCase()] = true; |
|
} |
|
return ret; |
|
} |
|
|
|
function nonEmptyAttrToDom(attr){ |
|
// summary: |
|
// Returns a setter function that copies the attribute to this.domNode, |
|
// or removes the attribute from this.domNode, depending on whether the |
|
// value is defined or not. |
|
return function(val){ |
|
domAttr[val ? "set" : "remove"](this.domNode, attr, val); |
|
this._set(attr, val); |
|
}; |
|
} |
|
|
|
function isEqual(a, b){ |
|
// summary: |
|
// Function that determines whether two values are identical, |
|
// taking into account that NaN is not normally equal to itself |
|
// in JS. |
|
|
|
return a === b || (/* a is NaN */ a !== a && /* b is NaN */ b !== b); |
|
} |
|
|
|
var _WidgetBase = declare("dijit._WidgetBase", [Stateful, Destroyable], { |
|
// summary: |
|
// Future base class for all Dijit widgets. |
|
// description: |
|
// Future base class for all Dijit widgets. |
|
// _Widget extends this class adding support for various features needed by desktop. |
|
// |
|
// Provides stubs for widget lifecycle methods for subclasses to extend, like postMixInProperties(), buildRendering(), |
|
// postCreate(), startup(), and destroy(), and also public API methods like set(), get(), and watch(). |
|
// |
|
// Widgets can provide custom setters/getters for widget attributes, which are called automatically by set(name, value). |
|
// For an attribute XXX, define methods _setXXXAttr() and/or _getXXXAttr(). |
|
// |
|
// _setXXXAttr can also be a string/hash/array mapping from a widget attribute XXX to the widget's DOMNodes: |
|
// |
|
// - DOM node attribute |
|
// | _setFocusAttr: {node: "focusNode", type: "attribute"} |
|
// | _setFocusAttr: "focusNode" (shorthand) |
|
// | _setFocusAttr: "" (shorthand, maps to this.domNode) |
|
// Maps this.focus to this.focusNode.focus, or (last example) this.domNode.focus |
|
// |
|
// - DOM node innerHTML |
|
// | _setTitleAttr: { node: "titleNode", type: "innerHTML" } |
|
// Maps this.title to this.titleNode.innerHTML |
|
// |
|
// - DOM node innerText |
|
// | _setTitleAttr: { node: "titleNode", type: "innerText" } |
|
// Maps this.title to this.titleNode.innerText |
|
// |
|
// - DOM node CSS class |
|
// | _setMyClassAttr: { node: "domNode", type: "class" } |
|
// Maps this.myClass to this.domNode.className |
|
// |
|
// - Toggle DOM node CSS class |
|
// | _setMyClassAttr: { node: "domNode", type: "toggleClass" } |
|
// Toggles myClass on this.domNode by this.myClass |
|
// |
|
// If the value of _setXXXAttr is an array, then each element in the array matches one of the |
|
// formats of the above list. |
|
// |
|
// If the custom setter is null, no action is performed other than saving the new value |
|
// in the widget (in this). |
|
// |
|
// If no custom setter is defined for an attribute, then it will be copied |
|
// to this.focusNode (if the widget defines a focusNode), or this.domNode otherwise. |
|
// That's only done though for attributes that match DOMNode attributes (title, |
|
// alt, aria-labelledby, etc.) |
|
|
|
// 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: "", |
|
_setIdAttr: "domNode", // to copy to this.domNode even for auto-generated id's |
|
|
|
// 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: "", |
|
// set on domNode even when there's a focus node. but don't set lang="", since that's invalid. |
|
_setLangAttr: nonEmptyAttrToDom("lang"), |
|
|
|
// dir: [const] String |
|
// 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". If undefined, widgets renders in page's |
|
// default direction. |
|
dir: "", |
|
// set on domNode even when there's a focus node. but don't set dir="", since that's invalid. |
|
_setDirAttr: nonEmptyAttrToDom("dir"), // to set on domNode even when there's a focus node |
|
|
|
// class: String |
|
// HTML class attribute |
|
"class": "", |
|
_setClassAttr: { node: "domNode", type: "class" }, |
|
|
|
// Override automatic assigning type --> focusNode, it causes exception on IE6-8. |
|
// Instead, type must be specified as ${type} in the template, as part of the original DOM. |
|
_setTypeAttr: null, |
|
|
|
// style: String||Object |
|
// HTML style attributes as cssText string or name/value hash |
|
style: "", |
|
|
|
// title: String |
|
// HTML title attribute. |
|
// |
|
// For form widgets this specifies a tooltip to display when hovering over |
|
// the widget (just like the native HTML title attribute). |
|
// |
|
// For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer, |
|
// etc., it's used to specify the tab label, accordion pane title, etc. In this case it's |
|
// interpreted as HTML. |
|
title: "", |
|
|
|
// tooltip: String |
|
// When this widget's title attribute is used to for a tab label, accordion pane title, etc., |
|
// this specifies the tooltip to appear when the mouse is hovered over that text. |
|
tooltip: "", |
|
|
|
// baseClass: [protected] String |
|
// Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate |
|
// widget state. |
|
baseClass: "", |
|
|
|
// 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 data-dojo-attach-point 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: |
|
// |
|
// | <div data-dojo-type=myWidget> |
|
// | <b> here's a plain DOM node |
|
// | <span data-dojo-type=subWidget>and a widget</span> |
|
// | <i> and another plain DOM node </i> |
|
// | </div> |
|
// |
|
// containerNode would point to: |
|
// |
|
// | <b> here's a plain DOM node |
|
// | <span data-dojo-type=subWidget>and a widget</span> |
|
// | <i> and another plain DOM node </i> |
|
// |
|
// In templated widgets, "containerNode" is set via a |
|
// data-dojo-attach-point 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, |
|
|
|
// ownerDocument: [const] Document? |
|
// The document this widget belongs to. If not specified to constructor, will default to |
|
// srcNodeRef.ownerDocument, or if no sourceRef specified, then to the document global |
|
ownerDocument: null, |
|
_setOwnerDocumentAttr: function(val){ |
|
// this setter is merely to avoid automatically trying to set this.domNode.ownerDocument |
|
this._set("ownerDocument", val); |
|
}, |
|
|
|
/*===== |
|
// _started: [readonly] Boolean |
|
// startup() has completed. |
|
_started: false, |
|
=====*/ |
|
|
|
// attributeMap: [protected] Object |
|
// Deprecated. Instead of attributeMap, widget should have a _setXXXAttr attribute |
|
// for each XXX attribute to be mapped to the DOM. |
|
// |
|
// 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 set('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 innerText |
|
// | title: { node: "titleNode", type: "innerText" } |
|
// Maps this.title to this.titleNode.innerText |
|
// |
|
// - 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: {}, |
|
|
|
// _blankGif: [protected] String |
|
// Path to a blank 1x1 image. |
|
// Used by `<img>` nodes in templates that really get their image via CSS background-image. |
|
_blankGif: config.blankGif || require.toUrl("dojo/resources/blank.gif"), |
|
|
|
// textDir: String |
|
// Bi-directional support, the main variable which is responsible for the direction of the text. |
|
// The text direction can be different than the GUI direction by using this parameter in creation |
|
// of a widget. |
|
// |
|
// This property is only effective when `has("dojo-bidi")` is defined to be true. |
|
// |
|
// Allowed values: |
|
// |
|
// 1. "" - default value; text is same direction as widget |
|
// 2. "ltr" |
|
// 3. "rtl" |
|
// 4. "auto" - contextual the direction of a text defined by first strong letter. |
|
textDir: "", |
|
|
|
//////////// INITIALIZATION METHODS /////////////////////////////////////// |
|
|
|
/*===== |
|
constructor: function(params, srcNodeRef){ |
|
// summary: |
|
// Create the widget. |
|
// params: Object|null |
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.) |
|
// and functions, typically callbacks like onClick. |
|
// The hash can contain any of the widget's properties, excluding read-only properties. |
|
// srcNodeRef: DOMNode|String? |
|
// 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 |
|
}, |
|
=====*/ |
|
|
|
_introspect: function(){ |
|
// summary: |
|
// Collect metadata about this widget (only once per class, not once per instance): |
|
// |
|
// - list of attributes with custom setters, storing in this.constructor._setterAttrs |
|
// - generate this.constructor._onMap, mapping names like "mousedown" to functions like onMouseDown |
|
|
|
var ctor = this.constructor; |
|
if(!ctor._setterAttrs){ |
|
var proto = ctor.prototype, |
|
attrs = ctor._setterAttrs = [], // attributes with custom setters |
|
onMap = (ctor._onMap = {}); |
|
|
|
// Items in this.attributeMap are like custom setters. For back-compat, remove for 2.0. |
|
for(var name in proto.attributeMap){ |
|
attrs.push(name); |
|
} |
|
|
|
// Loop over widget properties, collecting properties with custom setters and filling in ctor._onMap. |
|
for(name in proto){ |
|
if(/^on/.test(name)){ |
|
onMap[name.substring(2).toLowerCase()] = name; |
|
} |
|
|
|
if(/^_set[A-Z](.*)Attr$/.test(name)){ |
|
name = name.charAt(4).toLowerCase() + name.substr(5, name.length - 9); |
|
if(!proto.attributeMap || !(name in proto.attributeMap)){ |
|
attrs.push(name); |
|
} |
|
} |
|
} |
|
|
|
// Note: this isn't picking up info on properties like aria-label and role, that don't have custom setters |
|
// but that set() maps to attributes on this.domNode or this.focusNode |
|
} |
|
}, |
|
|
|
postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){ |
|
// summary: |
|
// Kicks off widget instantiation. See create() for details. |
|
// tags: |
|
// private |
|
|
|
// Note that we skip calling this.inherited(), i.e. dojo/Stateful::postscript(), because 1.x widgets don't |
|
// expect their custom setters to get called until after buildRendering(). Consider changing for 2.0. |
|
|
|
this.create(params, srcNodeRef); |
|
}, |
|
|
|
create: function(params, srcNodeRef){ |
|
// summary: |
|
// Kick off the life-cycle of a widget |
|
// description: |
|
// Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate, |
|
// etc.), some of which of you'll want to override. See http://dojotoolkit.org/reference-guide/dijit/_WidgetBase.html |
|
// for a discussion of the widget creation lifecycle. |
|
// |
|
// Of course, adventurous developers could override create entirely, but this should |
|
// only be done as a last resort. |
|
// params: Object|null |
|
// Hash of initialization parameters for widget, including scalar values (like title, duration etc.) |
|
// and functions, typically callbacks like onClick. |
|
// The hash can contain any of the widget's properties, excluding read-only properties. |
|
// srcNodeRef: DOMNode|String? |
|
// 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 |
|
// tags: |
|
// private |
|
|
|
// First time widget is instantiated, scan prototype to figure out info about custom setters etc. |
|
this._introspect(); |
|
|
|
// store pointer to original DOM tree |
|
this.srcNodeRef = dom.byId(srcNodeRef); |
|
|
|
// No longer used, remove for 2.0. |
|
this._connects = []; |
|
this._supportingWidgets = []; |
|
|
|
// this is here for back-compat, remove in 2.0 (but check NodeList-instantiate.html test) |
|
if(this.srcNodeRef && this.srcNodeRef.id && (typeof this.srcNodeRef.id == "string")){ |
|
this.id = this.srcNodeRef.id; |
|
} |
|
|
|
// mix in our passed parameters |
|
if(params){ |
|
this.params = params; |
|
lang.mixin(this, params); |
|
} |
|
this.postMixInProperties(); |
|
|
|
// Generate an id for the widget if one wasn't specified, or it was specified as id: undefined. |
|
// Do this before buildRendering() because it might expect the id to be there. |
|
if(!this.id){ |
|
this.id = registry.getUniqueId(this.declaredClass.replace(/\./g, "_")); |
|
if(this.params){ |
|
// if params contains {id: undefined}, prevent _applyAttributes() from processing it |
|
delete this.params.id; |
|
} |
|
} |
|
|
|
// The document and <body> node this widget is associated with |
|
this.ownerDocument = this.ownerDocument || (this.srcNodeRef ? this.srcNodeRef.ownerDocument : document); |
|
this.ownerDocumentBody = win.body(this.ownerDocument); |
|
|
|
registry.add(this); |
|
|
|
this.buildRendering(); |
|
|
|
var deleteSrcNodeRef; |
|
|
|
if(this.domNode){ |
|
// Copy attributes listed in attributeMap into the [newly created] DOM for the widget. |
|
// Also calls custom setters for all attributes with custom setters. |
|
this._applyAttributes(); |
|
|
|
// If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree. |
|
// For 2.0, move this after postCreate(). postCreate() shouldn't depend on the |
|
// widget being attached to the DOM since it isn't when a widget is created programmatically like |
|
// new MyWidget({}). See #11635. |
|
var source = this.srcNodeRef; |
|
if(source && source.parentNode && this.domNode !== source){ |
|
source.parentNode.replaceChild(this.domNode, source); |
|
deleteSrcNodeRef = true; |
|
} |
|
|
|
// Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId", |
|
// assuming that dojo._scopeName even exists in 2.0 |
|
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. |
|
// I think for back-compatibility it isn't deleting srcNodeRef until after postCreate() has run. |
|
if(deleteSrcNodeRef){ |
|
delete this.srcNodeRef; |
|
} |
|
|
|
this._created = true; |
|
}, |
|
|
|
_applyAttributes: function(){ |
|
// summary: |
|
// Step during widget creation to copy widget attributes to the |
|
// DOM according to attributeMap and _setXXXAttr objects, and also to call |
|
// custom _setXXXAttr() methods. |
|
// |
|
// 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. |
|
// |
|
// For backwards-compatibility reasons attributeMap overrides _setXXXAttr when |
|
// _setXXXAttr is a hash/string/array, but _setXXXAttr as a functions override attributeMap. |
|
// tags: |
|
// private |
|
|
|
// Call this.set() for each property that was either specified as parameter to constructor, |
|
// or is in the list found above. For correlated properties like value and displayedValue, the one |
|
// specified as a parameter should take precedence. |
|
// Particularly important for new DateTextBox({displayedValue: ...}) since DateTextBox's default value is |
|
// NaN and thus is not ignored like a default value of "". |
|
|
|
// Step 1: Save the current values of the widget properties that were specified as parameters to the constructor. |
|
// Generally this.foo == this.params.foo, except if postMixInProperties() changed the value of this.foo. |
|
var params = {}; |
|
for(var key in this.params || {}){ |
|
params[key] = this._get(key); |
|
} |
|
|
|
// Step 2: Call set() for each property with a non-falsy value that wasn't passed as a parameter to the constructor |
|
array.forEach(this.constructor._setterAttrs, function(key){ |
|
if(!(key in params)){ |
|
var val = this._get(key); |
|
if(val){ |
|
this.set(key, val); |
|
} |
|
} |
|
}, this); |
|
|
|
// Step 3: Call set() for each property that was specified as parameter to constructor. |
|
// Use params hash created above to ignore side effects from step #2 above. |
|
for(key in params){ |
|
this.set(key, params[key]); |
|
} |
|
}, |
|
|
|
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._TemplatedMixin`, which implements this method. |
|
// tags: |
|
// protected |
|
|
|
if(!this.domNode){ |
|
// Create root node if it wasn't created by _TemplatedMixin |
|
this.domNode = this.srcNodeRef || this.ownerDocument.createElement("div"); |
|
} |
|
|
|
// baseClass is a single class name or occasionally a space-separated list of names. |
|
// Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix. |
|
// TODO: make baseClass custom setter |
|
if(this.baseClass){ |
|
var classes = this.baseClass.split(" "); |
|
if(!this.isLeftToRight()){ |
|
classes = classes.concat(array.map(classes, function(name){ |
|
return name + "Rtl"; |
|
})); |
|
} |
|
domClass.add(this.domNode, classes); |
|
} |
|
}, |
|
|
|
postCreate: function(){ |
|
// summary: |
|
// Processing after the DOM fragment is created |
|
// description: |
|
// Called after the DOM fragment has been created, but not necessarily |
|
// added to the document. Do not include any operations which rely on |
|
// node dimensions or placement. |
|
// tags: |
|
// protected |
|
}, |
|
|
|
startup: function(){ |
|
// summary: |
|
// Processing after the DOM fragment is added to the document |
|
// description: |
|
// Called after a widget and its children have been created and added to the page, |
|
// and all related widgets have finished their create() cycle, up through postCreate(). |
|
// |
|
// Note that startup() may be called while the widget is still hidden, for example if the widget is |
|
// inside a hidden dijit/Dialog or an unselected tab of a dijit/layout/TabContainer. |
|
// For widgets that need to do layout, it's best to put that layout code inside resize(), and then |
|
// extend dijit/layout/_LayoutWidget so that resize() is called when the widget is visible. |
|
if(this._started){ |
|
return; |
|
} |
|
this._started = true; |
|
array.forEach(this.getChildren(), function(obj){ |
|
if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){ |
|
obj.startup(); |
|
obj._started = true; |
|
} |
|
}); |
|
}, |
|
|
|
//////////// DESTROY FUNCTIONS //////////////////////////////// |
|
|
|
destroyRecursive: function(/*Boolean?*/ preserveDom){ |
|
// summary: |
|
// Destroy this widget and its descendants |
|
// description: |
|
// This is the generic "destructor" function that all widget users |
|
// should call to cleanly discard with a widget. Once a widget is |
|
// destroyed, it is 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._TemplatedMixin widgets. |
|
|
|
this._beingDestroyed = true; |
|
this.destroyDescendants(preserveDom); |
|
this.destroy(preserveDom); |
|
}, |
|
|
|
destroy: function(/*Boolean*/ preserveDom){ |
|
// summary: |
|
// Destroy this widget, but not its descendants. Descendants means widgets inside of |
|
// this.containerNode. Will also destroy any resources (including widgets) registered via this.own(). |
|
// |
|
// This method will also destroy internal widgets such as those created from a template, |
|
// assuming those widgets exist inside of this.domNode but outside of this.containerNode. |
|
// |
|
// For 2.0 it's planned that this method will also destroy descendant widgets, so apps should not |
|
// depend on the current ability to destroy a widget without destroying its descendants. Generally |
|
// they should use destroyRecursive() for widgets with children. |
|
// preserveDom: Boolean |
|
// If true, this method will leave the original DOM structure alone. |
|
// Note: This will not yet work with _TemplatedMixin widgets |
|
|
|
this._beingDestroyed = true; |
|
this.uninitialize(); |
|
|
|
function destroy(w){ |
|
if(w.destroyRecursive){ |
|
w.destroyRecursive(preserveDom); |
|
}else if(w.destroy){ |
|
w.destroy(preserveDom); |
|
} |
|
} |
|
|
|
// Back-compat, remove for 2.0 |
|
array.forEach(this._connects, lang.hitch(this, "disconnect")); |
|
array.forEach(this._supportingWidgets, destroy); |
|
|
|
// Destroy supporting widgets, but not child widgets under this.containerNode (for 2.0, destroy child widgets |
|
// here too). if() statement is to guard against exception if destroy() called multiple times (see #15815). |
|
if(this.domNode){ |
|
array.forEach(registry.findWidgets(this.domNode, this.containerNode), destroy); |
|
} |
|
|
|
this.destroyRendering(preserveDom); |
|
registry.remove(this.id); |
|
this._destroyed = true; |
|
}, |
|
|
|
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){ |
|
domAttr.remove(this.domNode, "widgetId"); |
|
}else{ |
|
domConstruct.destroy(this.domNode); |
|
} |
|
delete this.domNode; |
|
} |
|
|
|
if(this.srcNodeRef){ |
|
if(!preserveDom){ |
|
domConstruct.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 |
|
array.forEach(this.getChildren(), function(widget){ |
|
if(widget.destroyRecursive){ |
|
widget.destroyRecursive(preserveDom); |
|
} |
|
}); |
|
}, |
|
|
|
uninitialize: function(){ |
|
// summary: |
|
// Deprecated. Override destroy() instead to implement custom widget tear-down |
|
// behavior. |
|
// tags: |
|
// protected |
|
return false; |
|
}, |
|
|
|
////////////////// GET/SET, CUSTOM SETTERS, ETC. /////////////////// |
|
|
|
_setStyleAttr: function(/*String||Object*/ value){ |
|
// summary: |
|
// Sets the style attribute 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.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(lang.isObject(value)){ |
|
domStyle.set(mapNode, value); |
|
}else{ |
|
if(mapNode.style.cssText){ |
|
mapNode.style.cssText += "; " + value; |
|
}else{ |
|
mapNode.style.cssText = value; |
|
} |
|
} |
|
|
|
this._set("style", value); |
|
}, |
|
|
|
_attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){ |
|
// summary: |
|
// Reflect a widget attribute (title, tabIndex, duration etc.) to |
|
// the widget DOM, as specified by commands parameter. |
|
// If commands isn't specified then it's looked up from attributeMap. |
|
// Note some attributes like "type" |
|
// cannot be processed this way as they are not mutable. |
|
// attr: |
|
// Name of member variable (ex: "focusNode" maps to this.focusNode) pointing |
|
// to DOMNode inside the widget, or alternately pointing to a subwidget |
|
// tags: |
|
// private |
|
|
|
commands = arguments.length >= 3 ? commands : this.attributeMap[attr]; |
|
|
|
array.forEach(lang.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, innerText, or attribute |
|
|
|
switch(type){ |
|
case "attribute": |
|
if(lang.isFunction(value)){ // functions execute in the context of the widget |
|
value = lang.hitch(this, value); |
|
} |
|
|
|
// Get the name of the DOM node attribute; usually it's the same |
|
// as the name of the attribute in the widget (attr), but can be overridden. |
|
// Also maps handler names to lowercase, like onSubmit --> onsubmit |
|
var attrName = command.attribute ? command.attribute : |
|
(/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr); |
|
|
|
if(mapNode.tagName){ |
|
// Normal case, mapping to a DOMNode. Note that modern browsers will have a mapNode.set() |
|
// method, but for consistency we still call domAttr |
|
domAttr.set(mapNode, attrName, value); |
|
}else{ |
|
// mapping to a sub-widget |
|
mapNode.set(attrName, value); |
|
} |
|
break; |
|
case "innerText": |
|
// Deprecated, use "textContent" instead. |
|
mapNode.innerHTML = ""; |
|
mapNode.appendChild(this.ownerDocument.createTextNode(value)); |
|
break; |
|
case "textContent": |
|
mapNode.textContent = value; |
|
break; |
|
case "innerHTML": |
|
mapNode.innerHTML = value; |
|
break; |
|
case "class": |
|
domClass.replace(mapNode, value, this[attr]); |
|
break; |
|
case "toggleClass": |
|
domClass.toggle(mapNode, command.className || attr, value); |
|
break; |
|
} |
|
}, this); |
|
}, |
|
|
|
get: function(name){ |
|
// summary: |
|
// Get a property from a widget. |
|
// name: |
|
// The property to get. |
|
// description: |
|
// Get a named property from a widget. The property may |
|
// potentially be retrieved via a getter method. If no getter is defined, this |
|
// just retrieves the object's property. |
|
// |
|
// For example, if the widget has properties `foo` and `bar` |
|
// and a method named `_getFooAttr()`, calling: |
|
// `myWidget.get("foo")` would be equivalent to calling |
|
// `widget._getFooAttr()` and `myWidget.get("bar")` |
|
// would be equivalent to the expression |
|
// `widget.bar2` |
|
var names = this._getAttrNames(name); |
|
return this[names.g] ? this[names.g]() : this._get(name); |
|
}, |
|
|
|
set: function(name, value){ |
|
// summary: |
|
// Set a property on a widget |
|
// name: |
|
// The property to set. |
|
// value: |
|
// The value to set in the property. |
|
// description: |
|
// Sets named properties on a widget which may potentially be handled by a |
|
// setter in the widget. |
|
// |
|
// For example, if the widget has properties `foo` and `bar` |
|
// and a method named `_setFooAttr()`, calling |
|
// `myWidget.set("foo", "Howdy!")` would be equivalent to calling |
|
// `widget._setFooAttr("Howdy!")` and `myWidget.set("bar", 3)` |
|
// would be equivalent to the statement `widget.bar = 3;` |
|
// |
|
// set() may also be called with a hash of name/value pairs, ex: |
|
// |
|
// | myWidget.set({ |
|
// | foo: "Howdy", |
|
// | bar: 3 |
|
// | }); |
|
// |
|
// This is equivalent to calling `set(foo, "Howdy")` and `set(bar, 3)` |
|
|
|
if(typeof name === "object"){ |
|
for(var x in name){ |
|
this.set(x, name[x]); |
|
} |
|
return this; |
|
} |
|
var names = this._getAttrNames(name), |
|
setter = this[names.s]; |
|
if(lang.isFunction(setter)){ |
|
// use the explicit setter |
|
var result = setter.apply(this, Array.prototype.slice.call(arguments, 1)); |
|
}else{ |
|
// Mapping from widget attribute to DOMNode/subwidget attribute/value/etc. |
|
// Map according to: |
|
// 1. attributeMap setting, if one exists (TODO: attributeMap deprecated, remove in 2.0) |
|
// 2. _setFooAttr: {...} type attribute in the widget (if one exists) |
|
// 3. apply to focusNode or domNode if standard attribute name, excluding funcs like onClick. |
|
// Checks if an attribute is a "standard attribute" by whether the DOMNode JS object has a similar |
|
// attribute name (ex: accept-charset attribute matches jsObject.acceptCharset). |
|
// Note also that Tree.focusNode() is a function not a DOMNode, so test for that. |
|
var defaultNode = this.focusNode && !lang.isFunction(this.focusNode) ? "focusNode" : "domNode", |
|
tag = this[defaultNode] && this[defaultNode].tagName, |
|
attrsForTag = tag && (tagAttrs[tag] || (tagAttrs[tag] = getAttrs(this[defaultNode]))), |
|
map = name in this.attributeMap ? this.attributeMap[name] : |
|
names.s in this ? this[names.s] : |
|
((attrsForTag && names.l in attrsForTag && typeof value != "function") || |
|
/^aria-|^data-|^role$/.test(name)) ? defaultNode : null; |
|
if(map != null){ |
|
this._attrToDom(name, value, map); |
|
} |
|
this._set(name, value); |
|
} |
|
return result || this; |
|
}, |
|
|
|
_attrPairNames: {}, // shared between all widgets |
|
_getAttrNames: function(name){ |
|
// summary: |
|
// Helper function for get() and set(). |
|
// 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.replace(/^[a-z]|-[a-zA-Z]/g, function(c){ |
|
return c.charAt(c.length - 1).toUpperCase(); |
|
}); |
|
return (apn[name] = { |
|
n: name + "Node", |
|
s: "_set" + uc + "Attr", // converts dashes to camel case, ex: accept-charset --> _setAcceptCharsetAttr |
|
g: "_get" + uc + "Attr", |
|
l: uc.toLowerCase() // lowercase name w/out dashes, ex: acceptcharset |
|
}); |
|
}, |
|
|
|
_set: function(/*String*/ name, /*anything*/ value){ |
|
// summary: |
|
// Helper function to set new value for specified property, and call handlers |
|
// registered with watch() if the value has changed. |
|
var oldValue = this[name]; |
|
this[name] = value; |
|
if(this._created && !isEqual(oldValue, value)){ |
|
if(this._watchCallbacks){ |
|
this._watchCallbacks(name, oldValue, value); |
|
} |
|
this.emit("attrmodified-" + name, { |
|
detail: { |
|
prevValue: oldValue, |
|
newValue: value |
|
} |
|
}); |
|
} |
|
}, |
|
|
|
_get: function(/*String*/ name){ |
|
// summary: |
|
// Helper function to get value for specified property stored by this._set(), |
|
// i.e. for properties with custom setters. Used mainly by custom getters. |
|
// |
|
// For example, CheckBox._getValueAttr() calls this._get("value"). |
|
|
|
// future: return name in this.props ? this.props[name] : this[name]; |
|
return this[name]; |
|
}, |
|
|
|
emit: function(/*String*/ type, /*Object?*/ eventObj, /*Array?*/ callbackArgs){ |
|
// summary: |
|
// Used by widgets to signal that a synthetic event occurred, ex: |
|
// | myWidget.emit("attrmodified-selectedChildWidget", {}). |
|
// |
|
// Emits an event on this.domNode named type.toLowerCase(), based on eventObj. |
|
// Also calls onType() method, if present, and returns value from that method. |
|
// By default passes eventObj to callback, but will pass callbackArgs instead, if specified. |
|
// Modifies eventObj by adding missing parameters (bubbles, cancelable, widget). |
|
// tags: |
|
// protected |
|
|
|
// Specify fallback values for bubbles, cancelable in case they are not set in eventObj. |
|
// Also set pointer to widget, although since we can't add a pointer to the widget for native events |
|
// (see #14729), maybe we shouldn't do it here? |
|
eventObj = eventObj || {}; |
|
if(eventObj.bubbles === undefined){ |
|
eventObj.bubbles = true; |
|
} |
|
if(eventObj.cancelable === undefined){ |
|
eventObj.cancelable = true; |
|
} |
|
if(!eventObj.detail){ |
|
eventObj.detail = {}; |
|
} |
|
eventObj.detail.widget = this; |
|
|
|
var ret, callback = this["on" + type]; |
|
if(callback){ |
|
ret = callback.apply(this, callbackArgs ? callbackArgs : [eventObj]); |
|
} |
|
|
|
// Emit event, but avoid spurious emit()'s as parent sets properties on child during startup/destroy |
|
if(this._started && !this._beingDestroyed){ |
|
on.emit(this.domNode, type.toLowerCase(), eventObj); |
|
} |
|
|
|
return ret; |
|
}, |
|
|
|
on: function(/*String|Function*/ type, /*Function*/ func){ |
|
// summary: |
|
// Call specified function when event occurs, ex: myWidget.on("click", function(){ ... }). |
|
// type: |
|
// Name of event (ex: "click") or extension event like touch.press. |
|
// description: |
|
// Call specified function when event `type` occurs, ex: `myWidget.on("click", function(){ ... })`. |
|
// Note that the function is not run in any particular scope, so if (for example) you want it to run in the |
|
// widget's scope you must do `myWidget.on("click", lang.hitch(myWidget, func))`. |
|
|
|
// For backwards compatibility, if there's an onType() method in the widget then connect to that. |
|
// Remove in 2.0. |
|
var widgetMethod = this._onMap(type); |
|
if(widgetMethod){ |
|
return aspect.after(this, widgetMethod, func, true); |
|
} |
|
|
|
// Otherwise, just listen for the event on this.domNode. |
|
return this.own(on(this.domNode, type, func))[0]; |
|
}, |
|
|
|
_onMap: function(/*String|Function*/ type){ |
|
// summary: |
|
// Maps on() type parameter (ex: "mousemove") to method name (ex: "onMouseMove"). |
|
// If type is a synthetic event like touch.press then returns undefined. |
|
var ctor = this.constructor, map = ctor._onMap; |
|
if(!map){ |
|
map = (ctor._onMap = {}); |
|
for(var attr in ctor.prototype){ |
|
if(/^on/.test(attr)){ |
|
map[attr.replace(/^on/, "").toLowerCase()] = attr; |
|
} |
|
} |
|
} |
|
return map[typeof type == "string" && type.toLowerCase()]; // String |
|
}, |
|
|
|
toString: function(){ |
|
// summary: |
|
// Returns a string that represents the widget. |
|
// description: |
|
// 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 reversible |
|
// serialization. |
|
return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String |
|
}, |
|
|
|
getChildren: function(){ |
|
// summary: |
|
// Returns all direct children of this widget, i.e. all widgets underneath this.containerNode whose parent |
|
// is this widget. Note that it does not return all descendants, but rather just direct children. |
|
// Analogous to [Node.childNodes](https://developer.mozilla.org/en-US/docs/DOM/Node.childNodes), |
|
// except containing widgets rather than DOMNodes. |
|
// |
|
// The result intentionally excludes internally created widgets (a.k.a. supporting widgets) |
|
// outside of this.containerNode. |
|
// |
|
// Note that the array returned is a simple array. Application code should not assume |
|
// existence of methods like forEach(). |
|
|
|
return this.containerNode ? registry.findWidgets(this.containerNode) : []; // dijit/_WidgetBase[] |
|
}, |
|
|
|
getParent: function(){ |
|
// summary: |
|
// Returns the parent widget of this widget. |
|
|
|
return registry.getEnclosingWidget(this.domNode.parentNode); |
|
}, |
|
|
|
connect: function(/*Object|null*/ obj, /*String|Function*/ event, /*String|Function*/ method){ |
|
// summary: |
|
// Deprecated, will be removed in 2.0, use this.own(on(...)) or this.own(aspect.after(...)) instead. |
|
// |
|
// Connects specified obj/event to specified method of this object |
|
// and registers for disconnect() on widget destroy. |
|
// |
|
// Provide widget-specific analog to dojo.connect, except with the |
|
// implicit use of this widget as the target object. |
|
// Events connected with `this.connect` are disconnected upon |
|
// destruction. |
|
// returns: |
|
// A handle that can be passed to `disconnect` in order to disconnect before |
|
// the widget is destroyed. |
|
// example: |
|
// | var btn = new 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 |
|
|
|
return this.own(connect.connect(obj, event, this, method))[0]; // handle |
|
}, |
|
|
|
disconnect: function(handle){ |
|
// summary: |
|
// Deprecated, will be removed in 2.0, use handle.remove() instead. |
|
// |
|
// Disconnects handle created by `connect`. |
|
// tags: |
|
// protected |
|
|
|
handle.remove(); |
|
}, |
|
|
|
subscribe: function(t, method){ |
|
// summary: |
|
// Deprecated, will be removed in 2.0, use this.own(topic.subscribe()) instead. |
|
// |
|
// Subscribes to the specified topic and calls the specified method |
|
// of this object and registers for unsubscribe() on widget destroy. |
|
// |
|
// Provide widget-specific analog to dojo.subscribe, except with the |
|
// implicit use of this widget as the target object. |
|
// t: String |
|
// The topic |
|
// method: Function |
|
// The callback |
|
// example: |
|
// | var btn = new Button(); |
|
// | // when /my/topic is published, this button changes its label to |
|
// | // be the parameter of the topic. |
|
// | btn.subscribe("/my/topic", function(v){ |
|
// | this.set("label", v); |
|
// | }); |
|
// tags: |
|
// protected |
|
return this.own(topic.subscribe(t, lang.hitch(this, method)))[0]; // handle |
|
}, |
|
|
|
unsubscribe: function(/*Object*/ handle){ |
|
// summary: |
|
// Deprecated, will be removed in 2.0, use handle.remove() instead. |
|
// |
|
// Unsubscribes handle created by this.subscribe. |
|
// Also removes handle from this widget's list of subscriptions |
|
// tags: |
|
// protected |
|
|
|
handle.remove(); |
|
}, |
|
|
|
isLeftToRight: function(){ |
|
// summary: |
|
// Return this widget's explicit or implicit orientation (true for LTR, false for RTL) |
|
// tags: |
|
// protected |
|
return this.dir ? (this.dir.toLowerCase() == "ltr") : domGeometry.isBodyLtr(this.ownerDocument); //Boolean |
|
}, |
|
|
|
isFocusable: function(){ |
|
// summary: |
|
// Return true if this widget can currently be focused |
|
// and false if not |
|
return this.focus && (domStyle.get(this.domNode, "display") != "none"); |
|
}, |
|
|
|
placeAt: function(/*String|DomNode|DocumentFragment|dijit/_WidgetBase*/ reference, /*String|Int?*/ position){ |
|
// summary: |
|
// Place this widget somewhere in the DOM based |
|
// on standard domConstruct.place() conventions. |
|
// description: |
|
// A convenience function provided in all _Widgets, providing a simple |
|
// shorthand mechanism to put an existing (or newly created) Widget |
|
// somewhere in the dom, and allow chaining. |
|
// reference: |
|
// Widget, DOMNode, DocumentFragment, or id of widget or DOMNode |
|
// position: |
|
// If reference is a widget (or id of widget), and that widget has an ".addChild" method, |
|
// it will be called passing this widget instance into that method, supplying the optional |
|
// position index passed. In this case position (if specified) should be an integer. |
|
// |
|
// If reference is a DOMNode (or id matching a DOMNode but not a widget), |
|
// the position argument can be a numeric index or a string |
|
// "first", "last", "before", or "after", same as dojo/dom-construct::place(). |
|
// returns: dijit/_WidgetBase |
|
// Provides a useful return of the newly created dijit._Widget instance so you |
|
// can "chain" this function by instantiating, placing, then saving the return value |
|
// to a variable. |
|
// example: |
|
// | // create a Button with no srcNodeRef, and place it in the body: |
|
// | var button = new Button({ label:"click" }).placeAt(win.body()); |
|
// | // now, 'button' is still the widget reference to the newly created button |
|
// | button.on("click", function(e){ console.log('click'); })); |
|
// example: |
|
// | // create a button out of a node with id="src" and append it to id="wrapper": |
|
// | var button = new Button({},"src").placeAt("wrapper"); |
|
// example: |
|
// | // place a new button as the first element of some div |
|
// | var button = new Button({ label:"click" }).placeAt("wrapper","first"); |
|
// example: |
|
// | // create a contentpane and add it to a TabContainer |
|
// | var tc = dijit.byId("myTabs"); |
|
// | new ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc) |
|
|
|
var refWidget = !reference.tagName && registry.byId(reference); |
|
if(refWidget && refWidget.addChild && (!position || typeof position === "number")){ |
|
// Adding this to refWidget and can use refWidget.addChild() to handle everything. |
|
refWidget.addChild(this, position); |
|
}else{ |
|
// "reference" is a plain DOMNode, or we can't use refWidget.addChild(). Use domConstruct.place() and |
|
// target refWidget.containerNode for nested placement (position==number, "first", "last", "only"), and |
|
// refWidget.domNode otherwise ("after"/"before"/"replace"). (But not supported officially, see #14946.) |
|
var ref = refWidget && ("domNode" in refWidget) ? |
|
(refWidget.containerNode && !/after|before|replace/.test(position || "") ? |
|
refWidget.containerNode : refWidget.domNode) : dom.byId(reference, this.ownerDocument); |
|
domConstruct.place(this.domNode, ref, position); |
|
|
|
// Start this iff it has a parent widget that's already started. |
|
// TODO: for 2.0 maybe it should also start the widget when this.getParent() returns null?? |
|
if(!this._started && (this.getParent() || {})._started){ |
|
this.startup(); |
|
} |
|
} |
|
return this; |
|
}, |
|
|
|
defer: function(fcn, delay){ |
|
// summary: |
|
// Wrapper to setTimeout to avoid deferred functions executing |
|
// after the originating widget has been destroyed. |
|
// Returns an object handle with a remove method (that returns null) (replaces clearTimeout). |
|
// fcn: Function |
|
// Function reference. |
|
// delay: Number? |
|
// Delay, defaults to 0. |
|
// tags: |
|
// protected |
|
|
|
var timer = setTimeout(lang.hitch(this, |
|
function(){ |
|
if(!timer){ |
|
return; |
|
} |
|
timer = null; |
|
if(!this._destroyed){ |
|
lang.hitch(this, fcn)(); |
|
} |
|
}), |
|
delay || 0 |
|
); |
|
return { |
|
remove: function(){ |
|
if(timer){ |
|
clearTimeout(timer); |
|
timer = null; |
|
} |
|
return null; // so this works well: handle = handle.remove(); |
|
} |
|
}; |
|
} |
|
}); |
|
|
|
if(has("dojo-bidi")){ |
|
_WidgetBase.extend(_BidiMixin); |
|
} |
|
|
|
return _WidgetBase; |
|
});
|
|
|