|
||||||||
PREV NEXT | FRAMES NO FRAMES |
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 );
|
||||||||
PREV NEXT | FRAMES NO FRAMES |