flavors.js

Summary

No overview generated for 'flavors.js'


// Note, the VAST MAJORITY of this file is comments, instructing
// people on how to create new flavors if necessary to get into
// other environments. You probably shouldn't ship this out to
// users with running it through "simpleCrunch.pl" or something
// to get rid of the comments.

/**
  Define a "flavor" of XBLinJS.

  @summary Working with Javascript on the web is famous for browser
  inconsistencies, to the point where a lot of people think they
  "hate Javascript" when what they really hate is the inconsistencies
  across browsers.

  <p>XBLinJS has it even worse; not only does it want to work across
  browsers in the conventional sense, it also wants to work in Mozilla
  with XUL, and even in non-browser things that use ECMAScript, like
  Flash, anything that uses the same general DOM event model. This
  makes merely programming cross-browser look like a piece of cake.</p>

  <p>XBLinJS addresses this issue by creating several "flavors" of
  XBLinJS, as defined in this file. These flavors provide methods that 
  the Widget class uses to perform its work, and does other things
  like managing constants. (For instance, the Browser
  flavor of XBLinJS has constants for determining what browser it is 
  running in, whereas the XUL flavor has no need of that, but it 
  may have other constants it wants to use.)</p>

  <p>The documentation for this class lays out the complete set of
  things a Flavor can change, which you should change in sub-classes 
  to create new flavors. If you find that you need things that aren't
  already in Flavor specifications, please mail the development list 
  on Sourceforge and I will be happy to work with you to pull the
  additional things out.</p>

  <p>This is a reverse template pattern, based on the way Javascript
  can dynamically choose a super-class to inherit from; this allows
  you to inherit from Widget, which is cleaner and simpler thing to
  do than constantly inheriting from "BrowserFlavor", and
  also means you can switch out flavors without affecting the rest of
  your code. (Odds are you'll never want to switch from
  Browser to Flash, but you may want to swap to a
  browser-specific flavor based on some criteria computed at run
  time.)</p>

  <p>One of the design requirements is that you can leave in extra
  flavors without causing any errors, so many computations must be
  wrapped up in functions that will only be executed if that flavor is
  used. For example, the Browser flavor has constants based on the 
  navigator.userAgent string, but other ECMAScript environments may
  not have this available at all, requiring either convoluted "if"
  statements or deferred execution. We have to protect that from
  execution until we know what environment the user desires. Your own
  flavors, of course, may do as they please.</p>

  <p>This is a "virtual class"; the methods named here are more for 
  the documentation opportunity than the implementation value. When 
  you override these, you do not need to call the superclass, EXCEPT
  if you happen to override .init, which should generally be
  called <i>before</i> your own .init, in case you want to
  override it.</p> 

  <p>(Note if you're going to create another Flavor that uses real XML
  like XUL does, it may be good to drop me a line and ask me to
  extract the XML support into a superclass that you can use without
  getting the XUL support in the way, such as the event handling.)</p>
*/
deriveNewJObject("Flavor", JObject);

/**
  copies all constants onto the object

  <p>This copies all the constants onto the object, and should always
  end up called, usually by Widget.</p>
*/
Flavor.prototype.initData = function (atts) {
  for (var name in this.flavorConstants) {
    this[name] = this.flavorConstants[name];
  }

  JObject.prototype.initData.call(this, atts);
}

/**
  Constants defined by this flavor.

  <p>Each flavor can define constants to use; they may either be
  actual values, or functions that will take no arguments and return
  a value, which will be resolved by resolveFlavor().</p>

  <p>Upon construction of a Flavor object, all constants are copied
  into the object, so bear that in mind while naming them. (They need 
  to be copied to allow sub-objects to selectively override them,
  which is highly useful.) Try to keep the number reasonable, although
  in general the time needed to copy the constants will most likely
  be swamped by other initialization.</p>
*/
Flavor.prototype.flavorConstants = {};

/**
  Resolve the deferred functions in a Flavor, copy relevant globals.

  <p>This resolves all the functions in a flavor that we deferred the
  execution of and prepares a Flavor for actual use, among other
  things copying relevant global functions out like setAttributeOn.</p>
*/
function resolveFlavor(flavor) {
  var proto = flavor.prototype;

  // run the fixups
  for (var fixupName in proto.flavorFixups) {
    var fixup = proto.flavorFixups[fixupName];
    fixup();
  }

  // copy the global functions out
  for (var name in {setAttributeOn: 0, getAttributeFrom: 0,
                    getElementsByTagName: 0}) {
    window[name] = proto[name];
  }

  for (var constName in proto.flavorConstants) {
    var constant = proto.flavorConstants[constName];
    if (typeof constant == FUNCTION_TYPE) {
      proto.flavorConstants[constName] = proto.flavorConstants[constName]();
    }
  }
}

/**
  code to run when a flavor is resolved for use

  <p>Certain flavors require certain code to be run in order to be
  used correctly; for instance the Browser flavor has to run code
  to restore the DOM constants such as ELEMENT_NODE that IE doesn't
  see fit to include. This object contains named functions that take
  no arguments and do whatever extra initialization the flavor needs 
  to run properly.</p>
*/
Flavor.prototype.flavorFixups = {};

/**
  creates a DOM node

  <p>This creates a DOM node with the given name. This is generally
  used to distinguish between a creation with a namespace, and a
  creation without. (If you need a new flavor, you can generally take
  either BrowserFlavor's implementation of this, or DOM2Flavor's
  implementation.)</p>
*/
Flavor.prototype.createElement = function (nodeType) {
}

/**
  attaches an event handler to a DOM node

  <p>This attaches an event handler to the given DOM node, which 
  regrettably varies widely from implementation to implementation.</p>

  <p>New flavors should try using the BrowserFlavor implementation of 
  this method or the DOM2Flavor.</p>

  @param node The DOM node to attach the event to.
  @param event The name of the event, <i>without</i> the "on". e.g.,
  <tt>click</tt>, <i>not</i> <tt>onclick</tt>.
  @param handler The function handler for the event.
*/
Flavor.prototype.attachHandlerToNode = function (node, event, handler) {
}

/**
  set an attribute on something, either DOM node or Widget

  <p>Regrettably, there are some subtleties involved with setting 
  attributes on nodes. I would have thought <tt>node.setAttribute(key,
  value)</tt> would always be the same as <tt>node[key] = value</tt>,
  but that is not true. What's worse is that to my knowledge, there is
  never a reason to allow one, but not the other.</p>

  <p>setAttributeOn should be used in preference to .setAttribute,
  unless you know that for all possible flavors, for all possible
  "key" values, the call will work correctly. The code shipped with
  XBLinJS tries to be very agnostic about it to maximize the value
  of the shipped widgets; your code, which will likely be more
  targetted, may not need to worry so much about .setAttribute. Still,
  some of the flavors do do some nice cross-platform flattening that
  you may wish to take advantage of.</p>

  <p>Flavors implementing this method should check to see if the
  node is a Widget, and if so, call .setAttribute on it. Otherwise,
  do what your Flavor needs to do with the key and value.</p>

  <p>Note that, strictly speaking, this isn't a "method", as it never
  uses <tt>this</tt>. This is an organizational tool, and
  <tt>resolveFlavor</tt> will move these functions into the global
  space.When you see .setAttributeOn, it's one of the Flavor
  functions.</p> 
*/
Flavor.prototype.setAttributeOn = function (node, key, value) {

}

/**
  Get an attribute from a node, be it Widget or DOM node.

  <p>Generally a complement to .setAttributeOn.</p>

  <p>Note that, strictly speaking, this isn't a "method", as it never
  uses <tt>this</tt>. This is an organizational tool, and
  <tt>resolveFlavor</tt> will move these functions into the global
  space.When you see .getAttributeFrom, it's one of the Flavor
  functions.</p> 
*/
Flavor.prototype.getAttributeFrom = function (node, key) {
}

/**
  Wraps the getElementsByTagName, because it can be either
  <tt>document.getElementsByTagName</tt> or
  <tt>document.getElementsByTagName<b>NS</b></tt>.

  @param tagName The tag name to retrieve, with the namespace as
  appropriate.
  @param document The document (or element) to call it on.
*/
Flavor.prototype.getElementsByTagName = function (tagName, node) {
}

/**
  BrowserFlavor - conventional browser flavor for XBLinJS

  @summary BrowserFlavor defines a version of XBLinJS for a conventional
  HTML browser. IE6 and Mozilla are tested, others are not but should
  probably go here.

  <p>It is possible that if cross-browser becomes too much to deal
  with, that we will actually create MozillaBrowserFlavor or IEFlavor,
  and dynamically select which flavor is the BrowserFlavor at runtime.
  Regardless, if you choose BrowserFlavor, you should get what you're
  looking for in your code.</p> 
*/
deriveNewJObject("BrowserFlavor", Flavor);

/**
  Browser constants

  <p>The constants are:</p>

  <ul>
    <li><b>isIE</b>: True if this is an IE browser.</li>
    <li><b>isGecko</b>: True if this is a Gecko browser: Mozilla, Firefox, 
        something like that.</li>
    <li><b>isSafari</b>: True if this is Safari.</li>
    <li><b>isKonqueror</b>: True if this is Konqueror.</li>
    </ul>

  <p>These should of course be used sparingly, but it can be
  unavoidable at times.</p>
*/
BrowserFlavor.prototype.flavorConstants = {
  isIE: function () { 
    var ua = navigator.userAgent.toLowerCase();
    return ((ua.indexOf("msie") != -1) && (ua.indexOf("opera") == -1) && (ua.indexOf("webtv") == -1));},
  isGecko: function () { 
    var ua = navigator.userAgent.toLowerCase();
    return (ua.indexOf("gecko") != -1);},
  isSafari: function () { 
    var ua = navigator.userAgent.toLowerCase();
    return (ua.indexOf("safari") != -1);},
  isKonqueror: function () { 
    var ua = navigator.userAgent.toLowerCase();
    return (ua.indexOf("konqueror") != -1);}
}

BrowserFlavor.prototype.getElementsByTagName = function (tagName, node) {
  return node.getElementsByTagName(tagName);
}

/**
  Browser fixups

  <p>The fixups are:</p>

  <ul>
    <li><b>IEDocumentTagConstants</b>: This function restores the
        DOM constants like ELEMENT_NODE on the document node;
        with this flavor you can always compare to
        <tt>document.ELEMENT_NODE</tt>. (It'll also fix
        up any browser with a similar error;
        <tt>document.ELEMENT_NODE</tt> is checked, not <tt>isIE</tt>.</li>
    </ul>
*/
BrowserFlavor.prototype.flavorFixups = {
  IEDocumentTagConstants: function () {
    var DOM_CONSTANT_LIST = ['ELEMENT_NODE', 'ATTRIBUTE_NODE',
       'TEXT_NODE', 'CDATA_SECTION_NODE', 'ENTITY_REFERENCE_NODE',
       'ENTITY_NODE', 'PROCESSING_INSTRUCTION_NODE', 'COMMENT_NODE',
       'DOCUMENT_NODE', 'DOCUMENT_TYPE_NODE', 'DOCUMENT_FRAGMENT_NODE',
       'NOTATION_NODE'];
    if (!document.ELEMENT_NODE) {
      try {
        for (var idx in DOM_CONSTANT_LIST) {
          idx = parseInt(idx);
          document[DOM_CONSTANT_LIST[idx]] = idx + 1;
        }
      } catch (e) { alert(e.message); }
    }
  }
}

/**
  create an element

  <p>In HTML mode, we assume DOM 1, which does not support namespaces
  via document.createElementNS. Create the node with
  document.createElement.</p>
*/
BrowserFlavor.prototype.createElement = function (nodeType) {
  return document.createElement(nodeType);
}

/**
  Attach an event handler to a node

  <p>The Browser flavor attaches an event to a node by setting the
  attribute of the node directly, e.g. <tt>node.onclick = func</tt>.</p>
*/
BrowserFlavor.prototype.attachHandlerToNode = function (node, event,
                                                        handler) {
  node["on" + event] = handler;
}

/**
  Set an attribute on the node, be it Widget or DOM node

  <p>This generally uses both <tt>node[key] = value</tt>, and
  <tt>node.setAttribute(key, value), with the following exceptions:</p>

  <ul>
    <li>Both <tt>class</tt> and <tt>className</tt> are flattened to
        <tt>node.className = value</tt>.</li>
    <!-- <li>If the key is <tt>style</tt>, it is broken into pieces and
        each piece is set on the relevant sub-object in style. For
        example, <tt>color: #FFFFFF; background-color: #000000</tt>
        will result in <tt>node.style.color = "#FFFFFF"; 
        node.style.backgroundColor = "#000000"</tt>; this is needed
        for IE compatibility and any other browsers that do this 
        same thing. (Note this does mean that you can't null out 
        a style by setting it to ""; you have to do it to all pieces,
        which is likely impossible. Use CSS style declarations as
        needed to get around this.)</li> -->
    </ul>
*/
BrowserFlavor.prototype.setAttributeOn = function (node, key, value) {
  if (node instanceof Widget) {
    node.setAttribute(key, value);
    return;
  }

  // FIXME: Handle style
  var lowerKey = key.toLowerCase();
  if (lowerKey == "class" || lowerKey == "classname") {
    node.className = value;
    return;
  }

  if (lowerKey != "style")
    node[key] = value;
  node.setAttribute(key, value);
}

/**
  Gets an attribute from a node, be it Widget or DOM node

  <p>Like setAttributeOn, this flattens <tt>class</tt> and
  <tt>className</tt> to <tt>className</tt>, but it doesn't
  try to deal with <tt>style</tt>. Avoid retrieving style with
  this method, or take your lumps on IE.</p>

  <p>Since it is possible for <tt>element[key] !=
  element.getAttribute(key)</tt>, this will try to use element[key],
  since that seems somewhat more reliable, but if that fails to
  come up with anything, will try element.getAttribute(key).
*/
BrowserFlavor.prototype.getAttributeFrom = function (node, key) {
  if (node instanceof Widget) {
    return node.getAttribute(key);
  }

  if (key == "class") key = "className";
  var directValue = node[key];
  if (directValue != undefined) return directValue;
  return node.getAttribute(key);
}

/**
  DOM2Flavor - XBLinJS flavor for use with XUL applications

  @summary DOM2Flavor uses an XML Namespace aware DOM2 implementation
  with <tt>createElementNS</tt>, and the <a
  href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html">DOM2
  events model</a> for handling events.

  <p>Among possibly other things, this turns XBLinJS into a direct
  replacement for XBL proper, usable with Mozilla's XUL. DOM2Flavor's
  constants are preloaded with a couple values that will help with this,
  including setting XUL's namespace as the default. You may want to
  change this in your code.</p>
*/
deriveNewJObject("DOM2Flavor", Flavor);

/**
  flavor constants for DOM2

  <p>The flavor constants are as follows:</p>

  <ul>
    <li><b>defaultURI</b>: The default URI to use for creating
        elements that do not have a colon in their name.
        This can be <i>extremely</i> useful as it becomes possible
        to use many HTML-based widgets in XUL by setting their
        defaultURI to the XHTML URI,
        <tt>http://www.w3.org/1999/xhtml</tt>. However, this defaults
        to the XUL URI for easy XUL Widget creation,
        <tt>http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul</tt>.
        </li>
    </ul>

*/
DOM2Flavor.prototype.flavorConstants = {
  defaultURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
}

/**
  create an element

  <p>This uses <tt>document.createElementNS</tt>. You have to register
  namespaces with the widget to use their mnemonic, like "xhtml:". See
  <tt>registerNamespace</tt>.
*/
DOM2Flavor.prototype.createElement = function (nodeType) {
  // apply doesn't work on createElementNS, at least in Moz 1.7
  var nsspec = this.processNamespace(nodeType);
  return document.createElementNS(nsspec[0], nsspec[1]);
}

/**
  Namespaces storage for registerNamespace

  <p>This stores the namespaces declared with registerNamespace. 
  By default, this starts with "xul" set to the XUL namespace,
  and "html" set to the XHTML namespace
  (<tt>http://www.w3.org/1999/xhtml</tt>), and "svg" set to the
  SVG namespace (<tt>http://www.w3.org/2000/svg</tt>), but you can add
  more.</p> 

  <p>This stores the namespaces as mnemonic->URI, i.e., 
  html: "http://www.w3.org/1999/xhtml".
*/
DOM2Flavor.prototype.namespaces = {
  xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
  html: "http://www.w3.org/1999/xhtml",
  svg: "http://www.w3.org/2000/svg"
}

/**
  Registration function for namespaces

  <p>This function registers namespaces for use in createElement and
  its derivatives.</p>

  <p>Note it directly writes DOM2Flavor.prototype.namespaces, so
  A: you can call it directly without a Widget reference via
  <tt>DOM2Flavor.prototype.registerNamespace()</tt> like any other
  function and B: If you override the namespaces object in a 
  subclass, this function will stop working unless you provide
  a fixed function in that subclass.</p>

  <p>Typically, you'll put this somewhere in your code as a simple
  function call, not something in a widget. It doesn't hurt to
  register a namespace over and over again, but it does no good,
  either.</p>

  @param mnemonic The name of the namespace, as used before the colon
  in the tagName, i.e., "html" or "xul".
  @param URI The URI of the namespace.
*/
DOM2Flavor.prototype.registerNamespace = function (mnemonic, URI) {
  DOM2Flavor.prototype.namespaces[mnemonic] = URI;
}

/**
  Returns a two element list, the namespaceURI and the qualified
  tag name.

  @private
*/
DOM2Flavor.prototype.processNamespace = function (tagName) {
  var index = tagName.indexOf(":");
  if (index == -1) {
    return [this.defaultURI, tagName];
  }
  var namespaceMnemonic = tagName.substr(0, index);
  tagName = tagName.substr(index + 1);
  return [this.namespaces[namespaceMnemonic], tagName];
}

DOM2Flavor.prototype.getElementsByTagName = function (tagName, node) {
  var nsspec = FlavorInstance.processNamespace(tagName);
  return node.getElementsByTagNameNS(nsspec[0], nsspec[1]);
}

/**
  Attach an event handler to a node

  <p>This uses the <tt>addEventListener</tt> interface defined by the
  DOM2 event model to add the event handler XBLinJS creates.</p>

  <p>In XUL, there is still some distinction between
  displayed nodes and non-displayed nodes; displayed nodes can
  have events bound to them via <tt>node.onclick = function (){}</tt>
  type syntax, but nodes not yet added to a document can only have
  event bindings added via addEventHandler. Reading the standard
  indicates that being able to use <tt>node.onclick</tt> <i>at all</i>
  is a non-standard bonus from the Mozilla people (probably added to
  shut up complaints from old-school Javascript programmers used to
  the HTML model), as that is defined as <a
href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-registration-html40">HTML
  4.0 event listeners</a> which doesn't apply at all to XUL, which
  even when using HTML is actually using <b>X</b>HTML.</p>

  <p>Note that there is some overlap with XBLinJS's event model and
  what DOM2 provides, but there are some differences as well; probably
  the most noticable is the XBLinJS <i>does</i> guarantee an event
  processing order, which allows for some applications not possible
  (or at least not safe) under the standard DOM2 Event model. A
  notable lack is that XBLinJS has no clean support for event
  capturing. However, I think in practice this will turn out
  unnecessary. If I am wrong about this, please report it and we
  will look into addressing the problem cleanly; I don't feel
  confident I can hack up a <i>good</i> solution without a real-world 
  use case.</p>

  <p>Note also that there could be wildly entertaining interactions 
  with the standard DOM event model if, in addition to using Widget
  event specifications, you <tt>addEventListener</tt>s to widget-owned 
  DOM nodes. If you are confident you understand both the DOM event
  model and the XBLinJS event model, the interactions should be
  well defined, but in general, I'd have to say I advise against
  mixing the two on the same DOM nodes.</p>
*/
DOM2Flavor.prototype.attachHandlerToNode = function (node, event,
                                                     handler) {
  node.addEventListener(event, handler, false);
}

/**
  Set attribute on the node, be it widget or DOM node

  <p>This, like createElement, uses setAttributeNS. For better
  or for worse, if a namespace is not given, the .defaultURI is
  used. The pain of this should be somewhat ameliorated by the fact
  that defaultURI can be set on a Widget-by-Widget basis, so hopefully
  in practice this is not annoying.</p>
*/
DOM2Flavor.prototype.setAttributeOn = function (node, key, value) {
  if (node instanceof Widget) {
    return node.setAttribute(key, value);
  }

  // see getAttributeFrom; it seems that with no explicit namespace,
  // we should use just plain setAttribute
  if (key.indexOf(":") == -1) {
    // fix for Moz, have to try other browsers
    if (key == "className") key = "class";
    node.setAttribute(key, value);
    return;
  }

  var processedKey = FlavorInstance.processNamespace(key);
  var uri = processedKey[0];
  var attName = processedKey[1];
  if (attName == "className") attName = "class";
  node.setAttributeNS(uri, attName, value);
}

/**
  Gets the attribute from a node, be it Widget or DOM node

  <p>Corresponding to .setAttribute, this just uses .getAttributeNS.</p>
*/
DOM2Flavor.prototype.getAttributeFrom = function (node, key) {
  if (node instanceof Widget) {
    return node.getAttribute(key);
  }

  // I'm not clear on correct behavior here, but it seems at least
  // in Mozilla that attributes without a namespace are in the
  // "" namespace, or the same one that getAttribute gets, so
  // only things with *explicit* namespaces should be run through
  // the namespace resolution sequence
  if (key.indexOf(":") == -1) {
    return node.getAttribute(key);
  }

  //if (!this.processNamespace) display(this);
  var processedKey = FlavorInstance.processNamespace(key);
  // apply doesn't work on this, at least not in Mozilla
  return node.getAttributeNS(processedKey[0], processedKey[1]);
}

/**
  XHTML flavor is the same as DOM2Flavor, but sets the default URI to 
  the XHTML one, not XUL.
*/
deriveNewJObject("XHTMLFlavor", DOM2Flavor);

XHTMLFlavor.prototype.flavorConstants = (
  tmpFlavorConstants = objCopy(DOM2Flavor.prototype.flavorConstants),
  tmpFlavorConstants.defaultURI = "http://www.w3.org/1999/xhtml",
  tmpFlavorConstants
);




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