/* Copyright (c) 2004-2006, The Dojo Foundation All Rights Reserved. Licensed under the Academic Free License version 2.1 or above OR the modified BSD license. For more information on Dojo licensing, see: http://dojotoolkit.org/community/licensing.shtml */ dojo.provide("dojo.widget.Widget"); dojo.require("dojo.lang.func"); dojo.require("dojo.lang.array"); dojo.require("dojo.lang.extras"); dojo.require("dojo.lang.declare"); dojo.require("dojo.ns"); dojo.require("dojo.widget.Manager"); dojo.require("dojo.event.*"); dojo.require("dojo.a11y"); dojo.declare("dojo.widget.Widget", null, function(){ // these properties aren't primitives and need to be created on a per-item // basis. // children: Array // a list of all of the widgets that have been added as children of // this component. Should only have values if isContainer is true. this.children = []; // extraArgs: Object // a map of properties which the widget system tried to assign from // user input but did not correspond to any of the properties set on // the class prototype. These names will also be available in all // lower-case form in this map this.extraArgs = {}; }, { // parent: Widget // the parent of this widget parent: null, // isTopLevel: Boolean // should this widget eat all events that bubble up to it? // obviously, top-level and modal widgets should set these appropriately isTopLevel: false, // disabled: Boolean // should this widget respond to user input? // in markup, this is specified as "disabled='disabled'", or just "disabled" disabled: false, // isContainer: Boolean // can this widget contain other widgets? isContainer: false, // widgetId: String // a unique, opaque ID string that can be assigned by users or by the // system. If the developer passes an ID which is known not to be // unique, the specified ID is ignored and the system-generated ID is // used instead. widgetId: "", // widgetType: String // used for building generic widgets widgetType: "Widget", // ns: String // defaults to 'dojo'. "namespace" is a reserved word in JavaScript, so we abbreviate ns: "dojo", getNamespacedType: function(){ // summary: // get the "full" name of the widget. If the widget comes from the // "dojo" namespace and is a Button, calling this method will // return "dojo:button", all lower-case return (this.ns ? this.ns + ":" + this.widgetType : this.widgetType).toLowerCase(); // String }, toString: function(){ // summary: // returns a string that represents the widget. When a widget is // cast to a string, this method will be used to generate the // output. Currently, it does not implement any sort of reversable // serialization. return '[Widget ' + this.getNamespacedType() + ', ' + (this.widgetId || 'NO ID') + ']'; // String }, repr: function(){ // summary: returns the string representation of the widget. return this.toString(); // String }, enable: function(){ // summary: // enables the widget, usually involving unmasking inputs and // turning on event handlers. Not implemented here. this.disabled = false; }, disable: function(){ // summary: // disables the widget, usually involves masking inputs and // unsetting event handlers. Not implemented here. this.disabled = true; }, // TODO: // 1) this would be better in HtmlWidget rather than here? // 2) since many widgets don't care if they've been resized, maybe this should be a mixin? onResized: function(){ // summary: // A signal that widgets will call when they have been resized. // Can be connected to for determining if a layout needs to be // reflowed. Clients should override this function to do special // processing, then call this.notifyChildrenOfResize() to notify // children of resize. this.notifyChildrenOfResize(); }, notifyChildrenOfResize: function(){ // summary: dispatches resized events to all children of this widget for(var i=0; i mixInProperties"); //dojo.profile.start(this.widgetType + " mixInProperties"); this.mixInProperties(args, fragment, parent); //dojo.profile.end(this.widgetType + " mixInProperties"); // dojo.debug(this.widgetType, "-> postMixInProperties"); //dojo.profile.start(this.widgetType + " postMixInProperties"); this.postMixInProperties(args, fragment, parent); //dojo.profile.end(this.widgetType + " postMixInProperties"); // dojo.debug(this.widgetType, "-> dojo.widget.manager.add"); dojo.widget.manager.add(this); // dojo.debug(this.widgetType, "-> buildRendering"); //dojo.profile.start(this.widgetType + " buildRendering"); this.buildRendering(args, fragment, parent); //dojo.profile.end(this.widgetType + " buildRendering"); // dojo.debug(this.widgetType, "-> initialize"); //dojo.profile.start(this.widgetType + " initialize"); this.initialize(args, fragment, parent); //dojo.profile.end(this.widgetType + " initialize"); // dojo.debug(this.widgetType, "-> postInitialize"); // postinitialize includes subcomponent creation // profile is put directly to function this.postInitialize(args, fragment, parent); // dojo.debug(this.widgetType, "-> postCreate"); //dojo.profile.start(this.widgetType + " postCreate"); this.postCreate(args, fragment, parent); //dojo.profile.end(this.widgetType + " postCreate"); // dojo.debug(this.widgetType, "done!"); //dojo.profile.end(this.widgetType + " create"); return this; }, destroy: function(finalize){ // summary: // Destroy this widget and it's descendants. This is the generic // "destructor" function that all widget users should call to // clealy discard with a widget. Once a widget is destroyed, it's // removed from the manager object. // finalize: Boolean // is this function being called part of global environment // tear-down? // FIXME: this is woefully incomplete if(this.parent){ this.parent.removeChild(this); } this.destroyChildren(); this.uninitialize(); this.destroyRendering(finalize); dojo.widget.manager.removeById(this.widgetId); }, destroyChildren: function(){ // summary: // Recursively destroy the children of this widget and their // descendents. var widget; var i=0; while(this.children.length > i){ widget = this.children[i]; if (widget instanceof dojo.widget.Widget) { // find first widget this.removeChild(widget); widget.destroy(); continue; } i++; // skip data object } }, getChildrenOfType: function(/*String*/type, recurse){ // summary: // return an array of descendant widgets who match the passed type // recurse: Boolean // should we try to get all descendants that match? Defaults to // false. var ret = []; var isFunc = dojo.lang.isFunction(type); if(!isFunc){ type = type.toLowerCase(); } for(var x=0; xsi)){ this[x][pairs[y].substr(0, si).replace(/^\s+|\s+$/g, "")] = pairs[y].substr(si+1); } } } }else{ // the default is straight-up string assignment. When would // we ever hit this? this[x] = args[x]; } } }else{ // collect any extra 'non mixed in' args this.extraArgs[x.toLowerCase()] = args[x]; } } // dojo.profile.end("mixInProperties"); }, postMixInProperties: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ // summary // Called after the parameters to the widget have been read-in, // but before the widget template is instantiated. // Especially useful to set properties that are referenced in the widget template. }, initialize: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ // summary: stub function. return false; // dojo.unimplemented("dojo.widget.Widget.initialize"); }, postInitialize: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ // summary: stub function. return false; }, postCreate: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ // summary: stub function. return false; }, uninitialize: function(){ // summary: // stub function. Over-ride to implement custom widget tear-down // behavior. return false; }, buildRendering: function(/*Object*/args, /*Object*/frag, /*Widget*/parent){ // summary: stub function. SUBCLASSES MUST IMPLEMENT dojo.unimplemented("dojo.widget.Widget.buildRendering, on "+this.toString()+", "); return false; }, destroyRendering: function(){ // summary: stub function. SUBCLASSES MUST IMPLEMENT dojo.unimplemented("dojo.widget.Widget.destroyRendering"); return false; }, addedTo: function(parent){ // summary: // stub function this is just a signal that can be caught // parent: Widget // instance of dojo.widget.Widget that we were added to }, addChild: function(child){ // summary: stub function. SUBCLASSES MUST IMPLEMENT dojo.unimplemented("dojo.widget.Widget.addChild"); return false; }, // Detach the given child widget from me, but don't destroy it removeChild: function(/*Widget*/widget){ // summary: // removes the passed widget instance from this widget but does // not destroy it for(var x=0; x dojo.widget.tags["dojo:connect"] = function(fragment, widgetParser, parentComp){ var properties = widgetParser.parseProperties(fragment["dojo:connect"]); } // FIXME: if we know the insertion point (to a reasonable location), why then do we: // - create a template node // - clone the template node // - render the clone and set properties // - remove the clone from the render tree // - place the clone // this is quite dumb dojo.widget.buildWidgetFromParseTree = function(/*String*/ type, /*Object*/ frag, /*dojo.widget.Parse*/ parser, /*Widget, optional*/ parentComp, /*int, optional*/ insertionIndex, /*Object*/ localProps){ // summary: creates a tree of widgets from the data structure produced by the first-pass parser (frag) // test for accessibility mode dojo.a11y.setAccessibleMode(); //dojo.profile.start("buildWidgetFromParseTree"); // FIXME: for codepath from createComponentFromScript, we are now splitting a path // that we already split and then joined var stype = type.split(":"); stype = (stype.length == 2) ? stype[1] : type; // FIXME: we don't seem to be doing anything with this! // var propertySets = parser.getPropertySets(frag); var localProperties = localProps || parser.parseProperties(frag[frag["ns"]+":"+stype]); var twidget = dojo.widget.manager.getImplementation(stype,null,null,frag["ns"]); if(!twidget){ throw new Error('cannot find "' + type + '" widget'); }else if (!twidget.create){ throw new Error('"' + type + '" widget object has no "create" method and does not appear to implement *Widget'); } localProperties["dojoinsertionindex"] = insertionIndex; // FIXME: we lose no less than 5ms in construction! var ret = twidget.create(localProperties, frag, parentComp, frag["ns"]); // dojo.profile.end("buildWidgetFromParseTree"); return ret; } dojo.widget.defineWidget = function(widgetClass, renderer, superclasses, init, props){ // summary: Create a widget constructor function (aka widgetClass) // widgetClass: String // the location in the object hierarchy to place the new widget class constructor // renderer: String // usually "html", determines when this delcaration will be used // superclasses: Function||Function[] // can be either a single function or an array of functions to be // mixed in as superclasses. If an array, only the first will be used // to set prototype inheritance. // init: Function // an optional constructor function. Will be called after superclasses are mixed in. // props: Object // a map of properties and functions to extend the class prototype with // This meta-function does parameter juggling for backward compat and overloading // if 4th argument is a string, we are using the old syntax // old sig: widgetClass, superclasses, props (object), renderer (string), init (function) if(dojo.lang.isString(arguments[3])){ dojo.widget._defineWidget(arguments[0], arguments[3], arguments[1], arguments[4], arguments[2]); }else{ // widgetClass var args = [ arguments[0] ], p = 3; if(dojo.lang.isString(arguments[1])){ // renderer, superclass args.push(arguments[1], arguments[2]); }else{ // superclass args.push('', arguments[1]); p = 2; } if(dojo.lang.isFunction(arguments[p])){ // init (function), props (object) args.push(arguments[p], arguments[p+1]); }else{ // props (object) args.push(null, arguments[p]); } dojo.widget._defineWidget.apply(this, args); } } dojo.widget.defineWidget.renderers = "html|svg|vml"; dojo.widget._defineWidget = function(widgetClass /*string*/, renderer /*string*/, superclasses /*function||array*/, init /*function*/, props /*object*/){ // FIXME: uncomment next line to test parameter juggling ... remove when confidence improves // dojo.debug('(c:)' + widgetClass + '\n\n(r:)' + renderer + '\n\n(i:)' + init + '\n\n(p:)' + props); // widgetClass takes the form foo.bar.baz<.renderer>.WidgetName (e.g. foo.bar.baz.WidgetName or foo.bar.baz.html.WidgetName) var module = widgetClass.split("."); var type = module.pop(); // type <= WidgetName, module <= foo.bar.baz<.renderer> var regx = "\\.(" + (renderer ? renderer + '|' : '') + dojo.widget.defineWidget.renderers + ")\\."; var r = widgetClass.search(new RegExp(regx)); module = (r < 0 ? module.join(".") : widgetClass.substr(0, r)); // deprecated in favor of namespace system, remove for 0.5 dojo.widget.manager.registerWidgetPackage(module); var pos = module.indexOf("."); var nsName = (pos > -1) ? module.substring(0,pos) : module; // FIXME: hrm, this might make things simpler //dojo.widget.tags.addParseTreeHandler(nsName+":"+type.toLowerCase()); props=(props)||{}; props.widgetType = type; if((!init)&&(props["classConstructor"])){ init = props.classConstructor; delete props.classConstructor; } dojo.declare(widgetClass, superclasses, init, props); }