widgets.js

Summary

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>&lt;table&gt;s must contain a &lt;tbody&gt; 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>&lt;body
 onload="runOnloadHandlers(window)"&gt;</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");




Documentation generated by JSDoc on Tue May 3 17:16:26 2005