|
||||||||
PREV NEXT | FRAMES NO FRAMES |
No overview generated for 'widgets.js'
// XBLinJS is a clone of the XBL framework for Mozilla, only // done up in Javascript. It is intended to match, to the extent // possible, the XBL framework feature for feature, but not // neceassarily "way of doing things" for "way of doing things", or // bug for bug; when JS provides a better way we take it. // Copyright (C) 2005 Jeremy Bowers // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // Contact and support is available through the Sourceforge project // "xblinjs", at https://sourceforge.net/projects/xblinjs/ . /** The flavor of XBLinJS chosen. <p>In the assignment, note it is checked if FLAVOR already exists. This allows you to override it in an assignment interleaved between loading jobject.js and widgets.js, or in another file interleaved when constructing a deployment version of this library.</p> */ if (!window.FLAVOR) { // NOTE: This is the *ONLY* place you should use to tweak this; // other code will call things like FLAVOR.prototype.whatever. // Change to DOM2Flavor (or whatever) right here: FLAVOR = BrowserFlavor; } // set up the flavor resolveFlavor(FLAVOR); // Create a FlavorInstance which is used behind the scenes by various // things FlavorInstance = new FLAVOR(); /** Maps the string names to references to the Widget (preventing the need to eval() them to get the reference) and provides a nice list of all known widget types, which can be useful in some scenarios. */ WidgetNameToType = {}; deriveNewWidget = deriveNewJObject; /** Store Widgets also in a WidgetNameToType object, so we can know that anything in that object is a Widget. */ function WidgetInit () { FLAVOR.prototype.initClass.call(this); WidgetNameToType[this.className] = JObjectNameToType[this.className]; } /** Widget is the base class that implements all of the features of the framework. Future widgets should be derived from this class. @summary Widget is the base class that XBLinJS is built on. It is too complicated to explain here; see <a href="../docWidgets.html">the documentation</a> . @param atts The attribute object, if constructing a widget for actual use. @param prototypeOnly If true, this object is only being used for its prototype, so don't initialize it as if it is going to be on the screen. */ deriveNewJObject("Widget", FLAVOR, undefined, WidgetInit); Widget.prototype.init = function (atts) { this.initDOM(atts); this.initWidget(atts); } /** Contains the content of the widget. See HTML documentation for details. <p>The default is a single span tag, but you should always override this; if you don't override it, that's a sign that class probably isn't really a Widget.</p> */ Widget.prototype.content = {tagName: "span", appendTarget: "default", children: ["This is the content of the Widget class; this usually" + " happens either because you didn't override the " + "content, or you left '.prototype.' out of the " + "assignment."] } /** Initializes data structures. <p>This should rarely, if ever, be overridden.</p> <p>Doing this separately prevents some problems with calling .processAtt before Widget.initDOM. Certain widget features still won't work (see .processAtt documentation), but at least they won't crash.</p> @private */ Widget.prototype.initData = function (atts) { Flavor.prototype.initData.call(this, atts); /** The window the widget belongs to. */ this.window = atts.window ? atts.window : window; if (atts.window) delete atts.window; // Give ourselves an id, so we can be found by number /** An ID for this widget in this window, mostly used so the .toString() method can return something useful. */ this.widgetId = this.getGlobals().maxId++; this.getGlobals().Widgets[this.widgetId] = this; /** Stores the append targets. @private */ this.appendTargets = {}; /** Stores the event handler chains. @private */ this.events = {}; /** Stores if we've connected a handler to the given event and node, which we can't get back out from DOM2 event listener info that I can see. */ this.eventHandlersRegistered = {}; } /** This initializes the data storage and DOM nodes of the widget. <p>This is generally only overridden if you want to affect the DOM before setting values and stuff.</p> @param atts The atts object containing the parameters, processed to include the defaults as needed. */ Widget.prototype.initDOM = function (atts) { // Create the content var content = this.content; if (content) { this.domNode = DOM(this.createObject(content)); } // register methods starting with "on" automatically as event // handlers for (var propName in this) { if (propName.substr(0, 2) == "on") { if (propName.indexOf("_") == -1) { this.registerEventHandler(propName.substr(2), propName); } else { var eventType = propName.substr(2, propName.indexOf("_")-2); var target = propName.substr(propName.indexOf("_") + 1); this.registerEventHandler(eventType, propName, target); } } } // declare the vars in this.Variables for (var idx in this.Variables) { var decl = this.Variables[idx]; this.declareVariable.apply(this, decl); this.createDefaultProperty(decl[0]); } if (!this.rootWidget) { // we must be our own root widget this.rootWidget = this; } } /** This runs through an event handler chain, and if any of them return false, it stops and returns false. Otherwise, this returns true. @private @param eventType The type of the HTML event, without the "on" prefix, e.g., "click", "focus", "keypress". @param target The target Widget of the event, as a .widgetId. Used to export event handling to some foreign widget. @param event The actual event object passed into Javascript by the browser. */ Widget.prototype.processEvent = function (eventType, target, event) { var handlers = this.events[eventType]; if (handlers == undefined) { // how'd this happen? Oh well... return true; } for (var idx in handlers) { var handler = handlers[idx]; var method = handler[0]; var eventTarget = handler[1]; var context = handler[2]; if (target == eventTarget) { var thisContext = context == undefined ? this : this.getGlobals().Widgets[context]; var result; result = thisContext[method].call(thisContext, event); if (result == false) { return false; } } } return true; } /** Registers an event handler. <p>Normally you should define the specially named methods to pick up events, but you can call this manually. This is necessary to attach to a foreign widget.</p> @param eventType The type of the event to catch, stripped of the "on" prefix. e.g., "blur", "focus", "keypress". @param handleMethod The name of the method to be called as the handler. Will be passed a single parameter, the event object from Javascript. Note the word "method"; only Widgets can handle events. (This may change later if there is call for it.) @param target The name of the DOM node or widget to recieve the event from, as seen from the widget you are calling registerEventHandler from. For example, if you are trying to pick up a click on a button, pass in the name corresponding to that button. This, if false, defaults to the domNode of the widget; this will often be the case when calling it manually unless you are tightly binding the two Widgets together (generally bad). This must be a string, not a reference, which implies events can only be registered on things with names. @context An advanced parameter: If you want some other widget other than the one recieving the object to handle the event, pass the Widget reference or .widgetId to this function, and the "handleMethod" will be called on that object instead. Not normally needed. */ Widget.prototype.registerEventHandler = function (eventType, handleMethod, target, context) { var origTarget = target; if (target) { target = this[target]; } else { target = this; } if (target == undefined) { throw ("Can't register event " + eventType + " on target " + origTarget + " because " + origTarget + " can't be found."); } // If the target is a widget itself, we need to tap into that // widget's event handling even though we want this context if (this != target && target instanceof Widget) { if (context == undefined) context = this.widgetId; return target.registerEventHandler(eventType, handleMethod, false, context); } if (this.events[eventType] == undefined) { this.events[eventType] = []; } var eventHandlerKeyName = eventType + "|" + origTarget; if (!this.eventHandlersRegistered[eventHandlerKeyName]) { this.eventHandlersRegistered[eventHandlerKeyName] = true; // only create and register a handler if we've never // put this handler on this widget before... in // HTML browser it is harmless to do that, but in // DOM2 applications it will cause the event to be // handled multiple times by our handlers. var handler = closureMaker({widget: this, origTarget:origTarget, eventType: eventType}, function (args, params) { var eventObj; // IE puts 'event' in the context. Mozilla passes it as an argument. // So look at the arguments.length to find out whether to pull the // events from the globals, or the arguments. if (args.length) { eventObj = args[0]; } else { eventObj = event; } with (params) return widget.processEvent(eventType, origTarget, eventObj); }); this.attachHandlerToNode(DOM(target), eventType, handler) } // HACK: To tide me over until I get rid of context-by-ID entirely. if (context instanceof Widget) { context = context.widgetId; } this.events[eventType].unshift([handleMethod, origTarget, context]); } /** This should do any final initialization of the widget now that we have all the DOM nodes, such as setting up the value of those nodes and such. <p>The default implementation does nothing, just a stub to prevent <tt>this.initWidget is not a function</tt> errors. */ Widget.prototype.initWidget = function (atts) { // any leftover atts get .setAttribute'ed FLAVOR.prototype.init.call(this, atts); } /** This method will be called when replaceElementWithWidget has finished adding any children there are for the tag. <p>This is only useful in the case where you are creating a widget that will primarily be invoked by tag. By default, it does nothing.</p> */ Widget.prototype.finishReplacementChildrenAdding = function () { return; } /** Recursively creates DOM nodes as described in the documentation. <p>child is true on recursive calls and is used for determining which DOM node gets the .widget attribute.</p> @private */ Widget.prototype.createObject = function (objDef, child) { if (!objDef) { return; } var object; // constants but I don't want them in the global namespace. var ELEMENT = 1; var WIDGET = 2; var objType; this.checkObjectDef(objDef); if (objDef.tagName) { object = this.createElement(objDef.tagName); for (var attName in objDef) { if ((attName) == "children") continue; if ((attName) == "tagName") continue; if ((attName) == "inherits") continue; setAttributeOn(object, attName, objDef[attName]); } if (!child) { object.widget = this; } objType = ELEMENT; } else if (objDef.widgetType) { if (typeof(objDef.widgetType) == STRING_TYPE) { if (WidgetNameToType[objDef.widgetType] == undefined) { throw ("Object type '" + objDef.widgetType + "' not found " +"in WidgetNameToType while constructing.") } objDef.widgetType = WidgetNameToType[objDef.widgetType]; } if (!(objDef.widgetType.prototype instanceof Widget) && !(objDef.widgetType === Widget)) { // FIXME: Isn't there a way to show the current class name? throw ("Argument 'widgetType' is not a widget or widget name."); } // set up the atts var atts = {}; for (var attName in objDef) { if (attName == "widgetType") continue; if (attName == "inherits") continue; if (attName == "name") continue; atts[attName] = objDef[attName]; } // same window as the root object atts.window = this.window; object = new objDef.widgetType(atts); object.rootWidget = this; objType = WIDGET; } else { try { var object = this.createObjectCustom(objDef, child); if (!object) { return undefined; } return object; } catch (e) { alert("Illegal object def in " + this.className + ";" + "exception thrown: " + e); } } if (objDef.appendTarget) { this.appendTargets[objDef.appendTarget] = object; } if (objDef.name) { this[objDef.name] = object; } if (objDef.inherits) { var inheritances = objDef.inherits.split(/\s*,\s*/); for (var idx in inheritances) { var outerField = inheritances[idx]; var innerField = outerField; // split off the extra "as" if necessary if (outerField.indexOf("=") != -1) { innerField = outerField.substr(0, outerField.indexOf("=")); outerField = outerField.substr(outerField.indexOf("=") + 1); } this.inherits.register(outerField, object, innerField); this.createDefaultProperty(outerField); } } // create the children, if any var childrenStack = []; var childIndexStack = []; count = 0; if (objDef.children) { childrenStack.push(objDef.children); childIndexStack.push(0); while(childIndexStack.length) { count ++; var thisIndex = childIndexStack[childIndexStack.length-1]; var thisChildren = childrenStack[childrenStack.length-1]; var child = thisChildren[thisIndex]; // hack; if we're nesting this deep, assume it's infinite if(count > 50) return; // If we're at the end of this one, pop it off if (thisIndex >= thisChildren.length || child == undefined) { childrenStack.pop(); childIndexStack.pop(); continue; } // If this is an array, push it onto the stack if (child instanceof Array) { childIndexStack[childIndexStack.length-1]++; childrenStack.push(child); childIndexStack.push(0); continue; } // Otherwise, make it. if (typeof child == STRING_TYPE) { var node = document.createTextNode(child); object.appendChild(node); } else { var node = DOM(this.createObject(child, 1)); if (node) { object.appendChild(node); } } childIndexStack[childIndexStack.length-1]++; } } return object; } /** This method should be overridden to perform any custom object creation. <p>The default method throws an error on all input.</p> */ Widget.prototype.createObjectCustom = function (objDef, child) { throw "Illegal object definition (and no createObjectCustom defined)."; } /** A function to issue various warnings with the object specs as they occur. <p>For any given children specification, the output of this function will be constant, so you can leave this in while deploying.</p> <p>This checks for the following common problems:</p> <ul> <li><table>s must contain a <tbody> in IE when being created via DOM manipulation.</li> <li>Using <tt>class</tt> on an object def to specify a CSS class doesn't work all the time in all browsers; use <tt>className</tt> instead (analogous to the direct-access attribute), and XBLinJS will try to take care of the rest.</li> </ul> */ Widget.prototype.checkObjectDef = function (def) { if (def.tagName && def['class']) { alert("Widget definition in class " + this.className + " has a 'class' attribute, which should be 'className'" + " to comply with how the JavaScript reflection works."); } if (def.tagName && def.tagName.toLowerCase() == "table") { // IE *demands* a TBODY in the table, for now, prompt the // developer var haveTBODY = !!def.hasTBody; for (var idx in def.children) { if (def.children[idx].tagName && def.children[idx].tagName.toLowerCase() == "tbody") { haveTBODY = true; } } if (!haveTBODY) { alert("Widget definition in class " + this.className + " has a <table> tag, but no <tbody>. This won't work in " +"IE 6.\n\n(If you actually have one, but due to "+ "complicated specifications this simple checking "+ "can't see it, add a 'hasTBody: true' to the table "+ "tag object, which will suppress this warning.)"); } } } /** Appends this widget to some DOM node. @param parent The DOM node (or Widget) to append this Widget to. */ Widget.prototype.appendTo = function (parent) { DOM(parent).appendChild(DOM(this)); } /** Appends a DOM node, text fragment, or widget to the specified append target. @param element The DOM node, text fragment (as string) or widget to be appended. @param target The append target to add the element to. Defaults to the append target named "default". */ Widget.prototype.appendChild = function (element, target) { if (target == undefined) { target = "default"; } var appendTarget = this.appendTargets[target]; if (!appendTarget) { throw "Append target '" + target + "' does not exist."; } if (typeof element == STRING_TYPE) { appendTarget.appendChild(textNode(element)); } else { if (!(appendTarget instanceof Widget)) { element = DOM(element); } appendTarget.appendChild(element); } } /** Prepends a DOM node, text fragment, or Widget to the specified append target. @param element The DOM node, text fragment (as string), or Widget to be prepended. @param target The append target to add the element to, defaulting to "default". */ Widget.prototype.prependChild = function (element, target) { if (target == undefined) { target = "default"; } var appendTarget = this.appendTargets[target]; if (!appendTarget) { throw "Append target '" + target + "' does not exist."; } if (typeof element == STRING_TYPE) { element = textNode(element); } appendTarget.insertBefore(DOM(element), DOM(appendTarget).childNodes[0]); } /** Returns a string identifying the type of the widget, and how to retrieve it: [type WidgetGlobals.Widgets[id]]. */ Widget.prototype.toString = function () { return ("[" + this.className + " WidgetGlobals.Widgets[" + this.widgetId + "]]"); } /** Returns the relevant global storage for this widget. <p>This should be used in preference to directly accessing WidgetGlobals, so the widget is more likely to work in cross-frame situations.</p> */ Widget.prototype.getGlobals = function () { return this.window.WidgetGlobals; } /** Shows the widget. <p>Equivalent to <tt>this.domNode.style.display = "". Widgets default to displaying just like anything else, of course, unless you do something.</p> */ Widget.prototype.show = function () { this.domNode.style.display = ""; } /** Hides the widget. <p>Equivalent to <tt>this.domNode.style.display = "none"</tt>.</p> */ Widget.prototype.hide = function () { this.domNode.style.display = "none"; } /** A convenience function for creating DOM elements, drawing on the XBLinJS flavor for the creation of the base widget. <p>This condenses five or more frequently-recurring lines down to a small handful, often just one, with much less redundancy.</p> @param tagName The name of the tag to create. @param attDict A Javascript object that will be used to initialize the tag by calling .setAttribute(key, value) on every key and value in the given object. @param childText A convenience parameter that, if defined, will add the given childText to the node. */ Widget.prototype.create = function (tagName, attDict, childText) { var element = this.createElement(tagName); if (attDict) { for (var key in attDict) { setAttributeOn(element, key, attDict[key]); } } if (childText) { element.appendChild(document.createTextNode(childText)); } return element; } /** A convenience function to create a text node with the given text. <p>All DOM implementations should do this the same, so this is a function, not a method.</p> @param text The contents of the text node. */ function textNode(text) { return document.createTextNode(text); } /** Creates a 'real' array from something that matches the array interface. <p>Useful for 'live' arrays like the return value from "Element.getElementsByTagName", which updates live as objects are removed from it. That's great, but makes iteration tough when you are modifying the targets. Also useful on the special Javascript variable <tt>arguments</tt>, which at least on Mozilla is not a real Array.</p> */ function arrayCopy(obj) { var realArray = []; for (var idx = 0; idx < obj.length; idx++) realArray.push(obj[idx]); return realArray; } /** For a given <widget> tag in a DOM document, replace it with a newly-created Widget, passed the attributes from the HTML tag. <p>You should generally use existing functions that use this, but you may need to call this directly for your own advanced uses.</p> <p>Any content in the tag will be passed to the newly-constructed widget via .appendChild calls; any children widget tags will be first converted to widgets. Returns the created widget, or the HTML DOM node passed in if no Widget was created.</p> <p>If an element is replaced, an attribute "XBLinJSreplaced" will be set to "1" on the DOM Node, and this function will not replace the element a second time.</p> <p>A special attribute "globalName" will be eaten by this function (not passed to the Widget creation function), and a new global variable will be created containing the resulting widget. You'll know when you need this.</p> @param element The DOM element to be replaced with a widget. This function actually doesn't care what type of element it is. @param targetWindow For advanced usage: specify the window object this widget will be a part of. For when you are using XBLinJS in a framed environment. See HTML documentation on "advanced uses". */ function replaceElementWithWidget (element, targetWindow) { if (!element.getAttribute) { // paranoia return element; } var widgetType = getAttributeFrom(element, "widgetType"); if (element.nodeType == document.ELEMENT_NODE && widgetType && !getAttributeFrom(element, "XBLinJSreplaced")) { var atts = {}; var name = getAttributeFrom(element, "name"); for (var attidx = 0; attidx < element.attributes.length; attidx++) { var att = element.attributes[attidx]; if (att.name != "widgetType" && att.name != "name") { atts[att.name.toLowerCase()] = att.value; } } var globalName = atts.globalname; if ("globalName" in atts) delete atts.globalname; if (!WidgetNameToType[widgetType]) { throw "Widget type '" + widgetType + "' isn't defined for replaceElementWithWidget"; } if (targetWindow) { atts.window = targetWindow; } var widget = new WidgetNameToType[widgetType](atts); setAttributeOn(element, "XBLinJSreplaced", "1"); element.parentNode.replaceChild(DOM(widget), element); if (globalName) { targetWindow[globalName] = widget; } var realChildren = arrayCopy(element.childNodes); for (var idx = 0; idx < realChildren.length; idx++) { var child = realChildren[idx]; // only bother with nodes that aren't effectively empty // text nodes if (child.nodeType != document.TEXT_NODE || !child.data.match(/^\s*$/)) { child = replaceElementWithWidget(child, targetWindow); widget.appendChild(child); } } widget.finishReplacementChildrenAdding(); if (name) { targetWindow.WidgetGlobals.replacedWithWidget[name] = widget; } return widget; } return element; } /** A function to replace <widget> tags with widgets. <p>This uses document.getElementsByTagName with a non-HTML tag name, which may cause problems with some browsers, or not; I'm being paranoid since it is very hard to test them all. Any content in the tag will be passed to the newly-constructed widget via .appendChild calls; any children widget tags will be first converted to widgets.</p> @param window: The target window to traverse. For advanced uses where XBLinJS is being used across frames: Due to the way scopes work, this function can only pick up the window it is defined in, not the one it was called in. Passing in the window will correctly create the widgets. Not doing this correctly currently causes silent failure; I need to detect this and give a better error. */ // TEST ME in the unit tests so we can easily do browser checks function ReplaceWidgetTags (targetWindow) { if (!targetWindow) targetWindow = window; var targetDocument = targetWindow.document; var widgetElements = getElementsByTagName("widget", targetDocument); // That returns an "HTMLCollection" which is // actually dynamically updated as we remove widgets. var realArray = arrayCopy(widgetElements); for (var idx = 0; idx < realArray.length; idx ++) { try { replaceElementWithWidget(realArray[idx], targetWindow); } catch (e) {} } } /** A function to create a closure, without having the passed-in variables change underneath you. <p>Javascript closures work on variable slots, not references, so creating multiple closures in a loop, which widgets.js sometimes does, can fail in unexpected ways. This function provides another frame for the objects to persist in, so each closure gets the intended arguments.</p> <p>The passed in function will recieve two arguments, "arguments", the Javascript arguments object for the function call, and the provided "params" object, which should be created new for each call (i.e., <b>do not pass</b> as a variable reference, create a new object with {}). From that you can extract all argument information and any of the information you passed in at creation time.</p> <p>(This has been a lifesaver; I'm not sure XBLinJS's event handling would be possible without this.)</p> @param params A Javascript object, created anew in the call itself, that contains whatever information you want the closed function to have. @param func The function to close on, with the given parameters. */ function closureMaker (params, func) { return function () { return func(arguments, params); }; } /** A function to run onloadHandlers as defined in WidgetGlobals. <p>This should be called like <tt><body onload="runOnloadHandlers(window)"></tt> for Widgets that set onload handlers. (None of the XBLinJS-provided Widgets do, but yours may.)</p> */ function runOnloadHandlers (targetWindow) { if (targetWindow == undefined) { targetWindow = window; } for (var idx in window.WidgetGlobals.onloadFunctions) { var func = window.WidgetGlobals.onloadFunctions[idx]; try { func(); } catch (e) {} } } /** A function to create "bound methods". <p>A "bound method" is like a function reference to a method, that also carries with it the object to call the method on. This is really useful for things like event handlers.</p> <p>You can take the return value of this function and call it as if you were calling object[methodName]().</p> <p>Design note: We take the method name, not a reference, to keep the resolution as late as possible. This allows you to do the Javascript things like re-assign methods in an object freely, and the result of this function will track it.</p> @param object The object to bind the method to. @param methodName The name of the method to bind to. */ function boundMethod(object, methodName) { return function () { return object[methodName].apply(object, arguments); } } /** @summary A ValueVar is the simplest type of DOMVariable. It binds to something that has a ".getAttribute('value')" and a ".setAttribute('value', val)" interface, usually an input field or a sub-widget. This is the default DOMVariable type used by Widgets if a type is not explicitly specified. @constructor @param widget The widget this ValueVar is being bound to. @param nodeName The DOM node or Widget to bind to. */ function ValueVar (widget, nodeName) { /** Stores the DOM node or widget this instance looks to for our value. */ this.input = DOM(widget[nodeName]); } ValueVar.prototype = new Variable(); /** Retrieve the 'value' of the targetted DOM node or widget. */ ValueVar.prototype.get = function () { return getAttributeFrom(this.input, "value"); } /** Set the value of the targetted DOM node or widget to the desired value. @param value The desired value. */ ValueVar.prototype.set = function (value) { // see get, same story setAttributeOn(this.input, "value", value); } /** * Given a DOM node or a Widget, flattens them into a DOM node * reference. Useful for agnostically using a Widget as a DOM node * for DOM node manipulation purposes. */ function DOM(node) { if (node instanceof Widget) { return node.domNode; } return node; } /** create an object suitable to be used as the WidgetGlobals in a frame <p>This function creates the widget globals needed by the framework. This will be automatically used in the current 'window', but if you trying to use the XBLinJS framework across framesets (which has definate advantages), you'll need to use this on the windows that don't load up 'widgets.js'. See the <a href="../widgetAdvancedUses.html">HTML documentation about "advanced uses"</a>.</p> */ function createWidgetGlobals() { var globals = {}; globals.maxId = 0; globals.Widgets = {}; // ID # -> widget globals.replacedWithWidget = {}; globals.onloadFunctions = []; return globals; } /** Per-frame globals needed by XBLinJS. */ WidgetGlobals = createWidgetGlobals(); // globalize the "create" method so we can call it as if it is // a function... now *this* is getting tricky... // called "widgetNamespace" because you can use it for other things widgetNamespace = new Widget(undefined, false); create = boundMethod(widgetNamespace, "create");
|
||||||||
PREV NEXT | FRAMES NO FRAMES |