- Author:
- David Nickerson <nickerso@users.sourceforge.net>
- Date:
- 2009-07-16 02:00:03+12:00
- Desc:
- the starting point for the HH tutorial example
- Permanent Source URI:
- https://models.fieldml.org/workspace/a1/rawfile/f6a8f90307388eb4b040ee3566b84d88b59247f7/dojo-presentation/js/dojo/dijit/_Widget.js
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){};
dojo.declare("dijit._Widget", null, {
// summary:
// The foundation of dijit widgets.
//
// id: 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: 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: 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
// HTML style attribute
style: "",
// title: String
// HTML title attribute
title: "",
// srcNodeRef: DomNode
// pointer to original dom node
srcNodeRef: null,
// 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 dojoAttachPoint syntax, but the domNode
// property is the canonical "top level" node in widget UI.
domNode: null,
// containerNode: 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 dojoType=myWidget>
// | <b> here's a plain dom node
// | <span dojoType=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 dojoType=subWidget>and a widget</span>
// | <i> and another plain dom node </i>
//
// 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: Object
// A map of attributes and attachpoints -- typically standard HTML attributes -- to set
// on the widget's dom, at the "domNode" attach point, by default.
// Other node references can be specified as properties of 'this'
attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""},
// _deferredConnects: 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
},
=====*/
onDblClick: dijit._connectOnUseEventHandler,
/*=====
onDblClick: function(event){
// summary:
// Connect to this function to receive notifications of mouse double click events.
// event: mouse Event
},
=====*/
onKeyDown: dijit._connectOnUseEventHandler,
/*=====
onKeyDown: function(event){
// summary:
// Connect to this function to receive notifications of keys being pressed down.
// event: key Event
},
=====*/
onKeyPress: dijit._connectOnUseEventHandler,
/*=====
onKeyPress: function(event){
// summary:
// Connect to this function to receive notifications of printable keys being typed.
// event: key Event
},
=====*/
onKeyUp: dijit._connectOnUseEventHandler,
/*=====
onKeyUp: function(event){
// summary:
// Connect to this function to receive notifications of keys being released.
// event: key Event
},
=====*/
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
},
=====*/
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
},
=====*/
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
},
=====*/
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
},
=====*/
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
},
=====*/
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
},
=====*/
onMouseUp: dijit._connectOnUseEventHandler,
/*=====
onMouseUp: function(event){
// summary:
// Connect to this function to receive notifications of when the mouse button is released.
// event: mouse Event
},
=====*/
// Constants used in templates
_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.
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
// 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.
for(attr in this.attributeMap){
var value = this[attr];
if((typeof value != "undefined") && (typeof value != "object") && ((value !== "" && value !== false) || (params && params[attr]))){
this.setAttribute(attr, value);
}
}
// 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;
}
},
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.
},
buildRendering: function(){
// summary:
// Construct the UI for this widget, setting this.domNode. Most
// widgets will mixin dijit._Templated, which implements this
// method.
this.domNode = this.srcNodeRef || dojo.doc.createElement('div');
},
postCreate: function(){
// summary:
// Called after a widget's dom has been setup
},
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.
if(this.bgIframe){
this.bgIframe.destroy(preserveDom);
delete this.bgIframe;
}
if(this.domNode){
if(!preserveDom){
dojo._destroyElement(this.domNode);
}
delete this.domNode;
}
if(this.srcNodeRef){
if(!preserveDom){
dojo._destroyElement(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.
// TODO: should I destroy in the reverse order, to go bottom up?
dojo.forEach(this.getDescendants(), function(widget){
if(widget.destroy){
widget.destroy(preserveDom);
}
});
},
uninitialize: function(){
// summary:
// stub function. Override to implement custom widget tear-down
// behavior.
return false;
},
////////////////// MISCELLANEOUS METHODS ///////////////////
onFocus: function(){
// summary:
// stub function. Override or connect to this method to receive
// notifications for when the widget moves into focus.
},
onBlur: function(){
// summary:
// stub function. Override or connect to this method to receive
// notifications for when the widget moves out of focus.
},
_onFocus: function(e){
this.onFocus();
},
_onBlur: function(){
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.
if(event in this._deferredConnects){
var mapNode = this[this._deferredConnects[event]||'domNode'];
this.connect(mapNode, event.toLowerCase(), this[event]);
delete this._deferredConnects[event];
}
},
setAttribute: function(/*String*/ attr, /*anything*/ value){
// summary:
// Set native HTML attributes reflected in the widget,
// such as readOnly, disabled, and maxLength in TextBox widgets.
// The placement of these attributes is according to the property mapping in attributeMap.
//
// description:
// Note special handling for 'style' and 'class' attributes which are lists and can
// have elements from both old and new structures, and some attributes like "type"
// cannot be processed this way as they are not mutable.
//
// Also, in general, a widget's "value" is controlled via setValue()/getValue(),
// rather than this method. The exception is for widgets where the
// end user can't adjust the value, such as Button and CheckBox;
// in the unusual case that you want to change the value attribute of
// those widgets, use setAttribute().
var mapNode = this[this.attributeMap[attr]||'domNode'];
this[attr] = value;
switch(attr){
case "class":
dojo.addClass(mapNode, value);
break;
case "style":
if(mapNode.style.cssText){
mapNode.style.cssText += "; " + value; // FIXME: Opera
}else{
mapNode.style.cssText = value;
}
break;
default:
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);
}
},
attr: function(/*String|Object*/name, /*Object?*/value){
// summary:
// Set or get properties on a widget instance. Unlike setAttribute(),
// attr() operates on the widget object itself instead of on DOM
// nodes which the widget may manage.
// 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 "setFoo", calling:
// | myWidget.attr("foo", "Howdy!");
// would be equivalent to calling:
// | widget.setFoo("Howdy!");
// while calling:
// | myWidget.attr("bar", "Howdy!");
// would be the same as writing:
// | widget.bar = "Howdy!";
// if the widget also contained a "barNode" property, the default
// setter behavior also attempts to change the content of that
// node if the passed value is a string. Therefore, these are
// equivalent:
// | widget.attr("bar", "Howdy!");
// and:
// | widget.bar = "Howdy!";
// | widget.barNode.innerHTML = "Howdy!";
// It works for dom node attributes too. Calling
// | widget.attr("disabled", true)
// is equivalent to calling
// | widget.setAttribute("disabled", true)
// 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;
}
names = this._getAttrNames(name);
if(args == 2){ // setter
if(this[names.s]){
// use the explicit setter
return this[names.s](value) || this;
}else if(this.set){
// default setter
return this.set(name, value) || this;
}else{
// if param is specified as DOM node attribute, copy it
if(name in this.attributeMap){
this.setAttribute(name, value);
}
// assign directly first
// FIXME: what about function assignments? Any way to connect() here?
this[name] = value;
// if attribute corresponds to some node's innerHTML, copy it
var node = this[names.n];
if(node && dojo.isString(value)){
if(dojo.isArray(node)){
dojo.forEach(node, function(i){ i.innerHTML = value; });
}else{
node.innerHTML = value;
}
}
}
return this;
}else{ // getter
if(this[names.g]){
return this[names.g]();
}else if(this.get){
return this.get(name);
}
return this[name];
}
},
_attrPairNames: {}, // shared between all widgets
_getAttrNames: function(name){
// summary: helper function for Widget.attr()
// cache attribute name values so we don't do the string ops every time
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: "_attrSet"+uc,
g: "_attrGet"+uc
};
},
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.
// description:
// This method is designed to *not* return widgets that are, for example,
// used as part of a template, but rather to just return widgets that are defined in the
// original markup as descendants of this widget, for example w/this markup:
//
// | <div dojoType=myWidget>
// | <b> hello world </b>
// | <div>
// | <span dojoType=subwidget>
// | <span dojoType=subwidget2>how's it going?</span>
// | </span>
// | </div>
// | </div>
//
// getDescendants() will return subwidget, but not anything that's part of the template
// of myWidget.
if(this.containerNode){
var list = dojo.query('[widgetId]', this.containerNode);
return list.map(dijit.byNode); // Array
}else{
return [];
}
},
// TODOC
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());
// | });
var d = dojo;
var dco = d.hitch(d, "connect", obj);
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(
dco("onkeydown", this, function(e){
if(!d.isFF && e.keyCode == d.keys.ENTER){
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);
}
}),
dco("onkeyup", this, function(e){
if(e.keyCode == d.keys.SPACE){ return m(e); }
})
);
if(d.isFF){
handles.push(
dco("onkeypress", this, function(e){
if(e.keyCode == d.keys.ENTER){ return m(e); }
})
);
}
}
event = "onclick";
}
handles.push(dco(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
for(var i=0; i<this._connects.length; i++){
if(this._connects[i]==handles){
dojo.forEach(handles, dojo.disconnect);
this._connects.splice(i, 1);
return;
}
}
},
isLeftToRight: function(){
// summary:
// Checks the page for text direction
return dojo._isBodyLtr(); //Boolean
},
isFocusable: function(){
// summary:
// Return true if this widget can currently be focused
// and false if not
return this.focus && (dojo.style(this.domNode, "display") != "none");
},
placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){
// summary: Place this widget's domNode reference somewhere in the DOM based
// on standard dojo.place conventions, or passing a Widget reference that
// contains and addChild member.
//
// 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:
// The String id of a domNode, a domNode reference, or a reference to a Widget posessing
// an addChild method.
//
// position:
// If passed a string or domNode reference, the position argument
// accepts a string just as dojo.place does, one of: "first", "last",
// "before", or "after".
//
// If passed a _Widget reference, and that widget reference has an ".addChild" method,
// it will be called passing this widget instance into that method, supplying the optional
// position index passed.
//
// example:
// | // create a Button with no srcNodeRef, and place it in the body:
// | var button = new dijit.form.Button({ label:"click" }).placeAt(dojo.body());
// | // now, 'button' is still the widget reference to the newly created button
// | dojo.connect(button, "onClick", 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 dijit.form.Button({},"src").placeAt("wrapper");
//
// example:
// | // place a new button as the first element of some div
// | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first");
//
// example:
// | // create a contentpane and add it to a TabContainer
// | var tc = dijit.byId("myTabs");
// | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
if(reference["declaredClass"] && reference["addChild"]){
reference.addChild(this, position);
}else{
dojo.place(this.domNode, reference, position);
}
return this;
}
});