diff --git a/src/core/init.js b/src/core/init.js index 6193924..cbfe0d4 100644 --- a/src/core/init.js +++ b/src/core/init.js @@ -1,216 +1,218 @@ /** * Javelin core; installs Javelin and Stratcom event delegation. * * @provides javelin-magical-init * * @javelin-installs JX.__rawEventQueue * @javelin-installs JX.__simulate + * @javelin-installs JX.__allowedEvents * @javelin-installs JX.enableDispatch * @javelin-installs JX.onload + * @javelin-installs JX.flushHoldingQueue * * @javelin */ (function() { if (window.JX) { return; } window.JX = {}; // The holding queues hold calls to functions (JX.install() and JX.behavior()) // before they load, so if you're async-loading them later in the document // the page will execute correctly regardless of the order resources arrive // in. var holding_queues = {}; function makeHoldingQueue(name) { if (JX[name]) { return; } holding_queues[name] = []; JX[name] = function() { holding_queues[name].push(arguments); } } JX.flushHoldingQueue = function(name, fn) { for (var ii = 0; ii < holding_queues[name].length; ii++) { fn.apply(null, holding_queues[name][ii]); } holding_queues[name] = {}; } makeHoldingQueue('install'); makeHoldingQueue('behavior'); makeHoldingQueue('install-init'); window['__DEV__'] = window['__DEV__'] || 0; var loaded = false; var onload = []; var master_event_queue = []; var root = document.documentElement; var has_add_event_listener = !!root.addEventListener; JX.__rawEventQueue = function(what) { master_event_queue.push(what); // Evade static analysis - JX.Stratcom var Stratcom = JX['Stratcom']; if (Stratcom && Stratcom.ready) { // Empty the queue now so that exceptions don't cause us to repeatedly // try to handle events. var local_queue = master_event_queue; master_event_queue = []; for (var ii = 0; ii < local_queue.length; ++ii) { var evt = local_queue[ii]; // Sometimes IE gives us events which throw when ".type" is accessed; // just ignore them since we can't meaningfully dispatch them. TODO: // figure out where these are coming from. try { var test = evt.type; } catch (x) { continue; } if (!loaded && evt.type == 'domready') { document.body && (document.body.id = null); loaded = true; for (var ii = 0; ii < onload.length; ii++) { onload[ii](); } } Stratcom.dispatch(evt); } } else { var target = what.srcElement || what.target; if (target && (what.type in {click: 1, submit: 1}) && target.getAttribute && target.getAttribute('data-mustcapture') === '1') { what.returnValue = false; what.preventDefault && what.preventDefault(); document.body.id = 'event_capture'; // For versions of IE that use attachEvent, the event object is somehow // stored globally by reference, and all the references we push to the // master_event_queue will always refer to the most recent event. We // work around this by popping the useless global event off the queue, // and pushing a clone of the event that was just fired using the IE's // proprietary createEventObject function. // see: http://msdn.microsoft.com/en-us/library/ms536390(v=vs.85).aspx if (!add_event_listener && document.createEventObject) { master_event_queue.pop(); master_event_queue.push(document.createEventObject(what)); } return false; } } } JX.enableDispatch = function(target, type) { if (__DEV__) { JX.__allowedEvents[type] = true; } if (target.addEventListener) { target.addEventListener(type, JX.__rawEventQueue, true); } else if (target.attachEvent) { target.attachEvent('on' + type, JX.__rawEventQueue); } }; var document_events = [ 'click', 'change', 'submit', 'keypress', 'mousedown', 'mouseover', 'mouseout', 'mouseup', 'keyup', 'keydown', 'drop', 'dragenter', 'dragleave', 'dragover', 'touchstart', 'touchmove', 'touchend', 'touchcancel' ]; // Simulate focus and blur in old versions of IE using focusin and focusout // TODO: Document the gigantic IE mess here with focus/blur. // TODO: beforeactivate/beforedeactivate? // http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html if (!has_add_event_listener) { document_events.push('focusin', 'focusout'); } // Opera is multilol: it propagates focus / blur oddly if (window.opera) { document_events.push('focus', 'blur'); } if (__DEV__) { JX.__allowedEvents = {}; if ('onpagehide' in window) { JX.__allowedEvents.unload = true; } } for (var ii = 0; ii < document_events.length; ++ii) { JX.enableDispatch(root, document_events[ii]); } // In particular, we're interested in capturing window focus/blur here so // long polls can abort when the window is not focused. var window_events = [ ('onpagehide' in window) ? 'pagehide' : 'unload', 'resize', 'focus', 'blur' ]; for (var ii = 0; ii < window_events.length; ++ii) { JX.enableDispatch(window, window_events[ii]); } JX.__simulate = function(node, event) { if (!has_add_event_listener) { var e = {target: node, type: event}; JX.__rawEventQueue(e); if (e.returnValue === false) { return false; } } }; if (has_add_event_listener) { document.addEventListener('DOMContentLoaded', function() { JX.__rawEventQueue({type: 'domready'}); }, true); } else { var ready = "if (this.readyState == 'complete') {" + "JX.__rawEventQueue({type: 'domready'});" + "}"; document.write( '<\/sc' + 'ript\>'); } JX.onload = function(func) { if (loaded) { func(); } else { onload.push(func); } } })(); diff --git a/src/core/install.js b/src/core/install.js index 9988633..8c1dbaa 100644 --- a/src/core/install.js +++ b/src/core/install.js @@ -1,436 +1,440 @@ /** * @requires javelin-util + * javelin-magical-init * @provides javelin-install + * * @javelin-installs JX.install + * @javelin-installs JX.createClass + * * @javelin */ /** * Install a class into the Javelin ("JX") namespace. The first argument is the * name of the class you want to install, and the second is a map of these * attributes (all of which are optional): * * - ##construct## //(function)// Class constructor. If you don't provide one, * one will be created for you (but it will be very boring). * - ##extend## //(string)// The name of another JX-namespaced class to extend * via prototypal inheritance. * - ##members## //(map)// A map of instance methods and properties. * - ##statics## //(map)// A map of static methods and properties. * - ##initialize## //(function)// A function which will be run once, after * this class has been installed. * - ##properties## //(map)// A map of properties that should have instance * getters and setters automatically generated for them. The key is the * property name and the value is its default value. For instance, if you * provide the property "size", the installed class will have the methods * "getSize()" and "setSize()". It will **NOT** have a property ".size" * and no guarantees are made about where install is actually chosing to * store the data. The motivation here is to let you cheaply define a * stable interface and refine it later as necessary. * - ##events## //(list)// List of event types this class is capable of * emitting. * * For example: * * JX.install('Dog', { * construct : function(name) { * this.setName(name); * }, * members : { * bark : function() { * // ... * } * }, * properites : { * name : null, * } * }); * * This creates a new ##Dog## class in the ##JX## namespace: * * var d = new JX.Dog(); * d.bark(); * * Javelin classes are normal Javascript functions and generally behave in * the expected way. Some properties and methods are automatically added to * all classes: * * - ##instance.__id__## Globally unique identifier attached to each instance. * - ##prototype.__class__## Reference to the class constructor. * - ##constructor.__path__## List of path tokens used emit events. It is * probably never useful to access this directly. * - ##constructor.__readable__## Readable class name. You could use this * for introspection. * - ##constructor.__events__## //DEV ONLY!// List of events supported by * this class. * - ##constructor.listen()## Listen to all instances of this class. See * @{JX.Base}. * - ##instance.listen()## Listen to one instance of this class. See * @{JX.Base}. * - ##instance.invoke()## Invoke an event from an instance. See @{JX.Base}. * * * @param string Name of the class to install. It will appear in the JX * "namespace" (e.g., JX.Pancake). * @param map Map of properties, see method documentation. * @return void * * @author epriestley */ JX.install = function(new_name, new_junk) { if (typeof JX.install._nextObjectID == 'undefined') { JX.install._nextObjectID = 0; } // If we've already installed this, something is up. if (new_name in JX) { if (__DEV__) { throw new Error( 'JX.install("' + new_name + '", ...): ' + 'trying to reinstall something that has already been installed.'); } return; } if (__DEV__) { if ('name' in new_junk) { throw new Error( 'JX.install("' + new_name + '", {"name": ...}): ' + 'trying to install with "name" property.' + 'Either remove it or call JX.createClass directly.'); } } // Since we may end up loading things out of order (e.g., Dog extends Animal // but we load Dog first) we need to keep a list of things that we've been // asked to install but haven't yet been able to install around. if (!JX.install._queue) { JX.install._queue = []; } JX.install._queue.push([new_name, new_junk]); do { var junk; var name = null; var initialize; for (var ii = 0; ii < JX.install._queue.length; ++ii) { junk = JX.install._queue[ii][1]; if (junk.extend && !JX[junk.extend]) { // We need to extend something that we haven't been able to install // yet, so just keep this in queue. continue; } // Install time! First, get this out of the queue. name = JX.install._queue.splice(ii, 1)[0][0]; --ii; if (junk.extend) { junk.extend = JX[junk.extend]; } initialize = junk.initialize; delete junk.initialize; junk.name = 'JX.' + name; JX[name] = JX.createClass(junk); if (initialize) { - if (JX.Stratcom && JX.Stratcom.ready) { + if (JX['Stratcom'] && JX['Stratcom'].ready) { initialize.apply(null); } else { // This is a holding queue, defined in init.js. JX['install-init'](initialize); } } } // In effect, this exits the loop as soon as we didn't make any progress // installing things, which means we've installed everything we have the // dependencies for. } while (name); }; /** * Creates a class from a map of attributes. Requires ##extend## property to * be an actual Class object and not a "String". Supports ##name## property * to give the created Class a readable name. * * @see JX.install for description of supported attributes. * * @param junk Map of properties, see method documentation. * @return function Constructor of a class created */ JX.createClass = function(junk) { if (typeof JX.install._nextObjectID == 'undefined') { JX.install._nextObjectID = 0; } var name = junk.name || ''; if (__DEV__) { var valid = { construct : 1, statics : 1, members : 1, extend : 1, properties : 1, events : 1, name : 1 }; for (var k in junk) { if (!(k in valid)) { throw new Error( 'JX.createClass("' + name + '", {"' + k + '": ...}): ' + 'trying to create unknown property `' + k + '`.'); } } if (junk.constructor !== {}.constructor) { throw new Error( 'JX.createClass("' + name + '", {"constructor": ...}): ' + 'property `constructor` should be called `construct`.'); } } // First, build the constructor. If construct is just a function, this // won't change its behavior (unless you have provided a really awesome // function, in which case it will correctly punish you for your attempt // at creativity). var Class = (function(name, junk) { var result = function() { this.__id__ = '__obj__' + (++JX.install._nextObjectID); return (junk.construct || junk.extend || JX.bag).apply(this, arguments); // TODO: Allow mixins to initialize here? // TODO: Also, build mixins? }; if (__DEV__) { var inner = result; result = function() { if (this == window || this == JX) { throw new Error( '<' + Class.__readable__ + '>: ' + 'Tried to construct an instance without the "new" operator.'); } return inner.apply(this, arguments); }; } return result; })(name, junk); Class.__readable__ = name; // Copy in all the static methods and properties. for (var k in junk.statics) { // Can't use JX.copy() here yet since it may not have loaded. Class[k] = junk.statics[k]; } var proto; if (junk.extend) { var Inheritance = function() {}; Inheritance.prototype = junk.extend.prototype; proto = Class.prototype = new Inheritance(); } else { proto = Class.prototype = {}; } proto.__class__ = Class; // Build getters and setters from the `prop' map. for (var k in (junk.properties || {})) { var base = k.charAt(0).toUpperCase()+k.substr(1); var prop = '__auto__' + k; proto[prop] = junk.properties[k]; proto['set' + base] = (function(prop) { return function(v) { this[prop] = v; return this; }; })(prop); proto['get' + base] = (function(prop) { return function() { return this[prop]; }; })(prop); } if (__DEV__) { // Check for aliasing in default values of members. If we don't do this, // you can run into a problem like this: // // JX.install('List', { members : { stuff : [] }}); // // var i_love = new JX.List(); // var i_hate = new JX.List(); // // i_love.stuff.push('Psyduck'); // I love psyduck! // JX.log(i_hate.stuff); // Show stuff I hate. // // This logs ["Psyduck"] because the push operation modifies // JX.List.prototype.stuff, which is what both i_love.stuff and // i_hate.stuff resolve to. To avoid this, set the default value to // null (or any other scalar) and do "this.stuff = [];" in the // constructor. for (var member_name in junk.members) { if (junk.extend && member_name[0] == '_') { throw new Error( 'JX.createClass("' + name + '", ...): ' + 'installed member "' + member_name + '" must not be named with ' + 'a leading underscore because it is in a subclass. Variables ' + 'are analyzed and crushed one file at a time, and crushed ' + 'member variables in subclasses alias crushed member variables ' + 'in superclasses. Remove the underscore, refactor the class so ' + 'it does not extend anything, or fix the minifier to be ' + 'capable of safely crushing subclasses.'); } var member_value = junk.members[member_name]; if (typeof member_value == 'object' && member_value !== null) { throw new Error( 'JX.createClass("' + name + '", ...): ' + 'installed member "' + member_name + '" is not a scalar or ' + 'function. Prototypal inheritance in Javascript aliases object ' + 'references across instances so all instances are initialized ' + 'to point at the exact same object. This is almost certainly ' + 'not what you intended. Make this member static to share it ' + 'across instances, or initialize it in the constructor to ' + 'prevent reference aliasing and give each instance its own ' + 'copy of the value.'); } } } // This execution order intentionally allows you to override methods // generated from the "properties" initializer. for (var k in junk.members) { proto[k] = junk.members[k]; } // Build this ridiculous event model thing. Basically, this defines // two instance methods, invoke() and listen(), and one static method, // listen(). If you listen to an instance you get events for that // instance; if you listen to a class you get events for all instances // of that class (including instances of classes which extend it). // // This is rigged up through Stratcom. Each class has a path component // like "class:Dog", and each object has a path component like // "obj:23". When you invoke on an object, it emits an event with // a path that includes its class, all parent classes, and its object // ID. // // Calling listen() on an instance listens for just the object ID. // Calling listen() on a class listens for that class's name. This // has the effect of working properly, but installing them is pretty // messy. var parent = junk.extend || {}; var old_events = parent.__events__; var new_events = junk.events || []; var has_events = old_events || new_events.length; if (has_events) { var valid_events = {}; // If we're in dev, we build up a list of valid events (for this class // and our parent class), and then check them on listen and invoke. if (__DEV__) { for (var key in old_events || {}) { valid_events[key] = true; } for (var ii = 0; ii < new_events.length; ++ii) { valid_events[junk.events[ii]] = true; } } Class.__events__ = valid_events; // Build the class name chain. Class.__name__ = 'class:' + name; var ancestry = parent.__path__ || []; Class.__path__ = ancestry.concat([Class.__name__]); proto.invoke = function(type) { if (__DEV__) { if (!(type in this.__class__.__events__)) { throw new Error( this.__class__.__readable__ + '.invoke("' + type + '", ...): ' + 'invalid event type. Valid event types are: ' + JX.keys(this.__class__.__events__).join(', ') + '.'); } } // Here and below, this nonstandard access notation is used to mask // these callsites from the static analyzer. JX.Stratcom is always // available by the time we hit these execution points. return JX['Stratcom'].invoke( 'obj:' + type, this.__class__.__path__.concat([this.__id__]), {args : JX.$A(arguments).slice(1)}); }; proto.listen = function(type, callback) { if (__DEV__) { if (!(type in this.__class__.__events__)) { throw new Error( this.__class__.__readable__ + '.listen("' + type + '", ...): ' + 'invalid event type. Valid event types are: ' + JX.keys(this.__class__.__events__).join(', ') + '.'); } } return JX['Stratcom'].listen( 'obj:' + type, this.__id__, JX.bind(this, function(e) { return callback.apply(this, e.getData().args); })); }; Class.listen = function(type, callback) { if (__DEV__) { if (!(type in this.__events__)) { throw new Error( this.__readable__ + '.listen("' + type + '", ...): ' + 'invalid event type. Valid event types are: ' + JX.keys(this.__events__).join(', ') + '.'); } } return JX['Stratcom'].listen( 'obj:' + type, this.__name__, JX.bind(this, function(e) { return callback.apply(this, e.getData().args); })); }; } else if (__DEV__) { var error_message = 'class does not define any events. Pass an "events" property to ' + 'JX.createClass() to define events.'; Class.listen = Class.listen || function() { throw new Error( this.__readable__ + '.listen(...): ' + error_message); }; Class.invoke = Class.invoke || function() { throw new Error( this.__readable__ + '.invoke(...): ' + error_message); }; proto.listen = proto.listen || function() { throw new Error( this.__class__.__readable__ + '.listen(...): ' + error_message); }; proto.invoke = proto.invoke || function() { throw new Error( this.__class__.__readable__ + '.invoke(...): ' + error_message); }; } return Class; }; JX.flushHoldingQueue('install', JX.install); diff --git a/src/core/util.js b/src/core/util.js index d65d652..29358fd 100644 --- a/src/core/util.js +++ b/src/core/util.js @@ -1,307 +1,307 @@ /** * Javelin utility functions. * - * @requires javelin-magical-init * @provides javelin-util * * @javelin-installs JX.$A * @javelin-installs JX.$AX * @javelin-installs JX.copy * @javelin-installs JX.bind * @javelin-installs JX.bag * @javelin-installs JX.keys * @javelin-installs JX.defer * @javelin-installs JX.log + * @javelin-installs JX.id * * @javelin */ /** * Convert an array-like object (usually ##arguments##) into a real Array. An * "array-like object" is something with a ##length## property and numerical * keys. The most common use for this is to let you call Array functions on the * magical ##arguments## object. * * JX.$A(arguments).slice(1); * * @param obj Array, or array-like object. * @return Array Actual array. */ JX.$A = function(mysterious_arraylike_object) { // NOTE: This avoids the Array.slice() trick because some bizarre COM object // I dug up somewhere was freaking out when I tried to do it and it made me // very upset, so do not replace this with Array.slice() cleverness. var r = []; for (var ii = 0; ii < mysterious_arraylike_object.length; ii++) { r.push(mysterious_arraylike_object[ii]); } return r; }; /** * Cast a value into an array, by wrapping scalars into singletons. If the * argument is an array, it is returned unmodified. If it is a scalar, an array * with a single element is returned. For example: * * JX.$AX([3]); // Returns [3]. * JX.$AX(3); // Returns [3]. * * Note that this function uses an "instanceof Array" check so you may need to * convert array-like objects (such as ##arguments## and Array instances from * iframes) into real arrays with @{JX.$A()}. * * @param wild Scalar or Array. * @return Array If the argument was a scalar, an Array with the argument as * its only element. Otherwise, the original Array. * */ JX.$AX = function(maybe_scalar) { return (maybe_scalar instanceof Array) ? maybe_scalar : [maybe_scalar]; }; /** * Copy properties from one object to another. Note: does not copy the * ##toString## property or anything else which isn't enumerable or is somehow * magic or just doesn't work. But it's usually what you want. If properties * already exist, they are overwritten. * * var cat = { * ears: 'clean', * paws: 'clean', * nose: 'DIRTY OH NOES' * }; * var more = { * nose: 'clean', * tail: 'clean' * }; * * JX.copy(cat, more); * * // cat is now: * // { * // ears: 'clean', * // paws: 'clean', * // nose: 'clean', * // tail: 'clean' * // } * * @param obj Destination object, which properties should be copied to. * @param obj Source object, which properties should be copied from. * @return obj Destination object. */ JX.copy = function(copy_dst, copy_src) { for (var k in copy_src) { copy_dst[k] = copy_src[k]; } return copy_dst; }; /** * Create a function which invokes another function with a bound context and * arguments (i.e., partial function application) when called; king of all * functions. * * Bind performs context binding (letting you select what the value of ##this## * will be when a function is invoked) and partial function application (letting * you create some function which calls another one with bound arguments). * * = Context Binding = * * Normally, when you call ##obj.method()##, the magic ##this## object will be * the ##obj## you invoked the method from. This can be undesirable when you * need to pass a callback to another function. For instance: * * COUNTEREXAMPLE * var dog = new JX.Dog(); * dog.barkNow(); // Makes the dog bark. * * JX.Stratcom.listen('click', 'bark', dog.barkNow); // Does not work! * * This doesn't work because ##this## is ##window## when the function is * later invoked; @{JX.Stratcom.listen()} does not know about the context * object ##dog##. The solution is to pass a function with a bound context * object: * * var dog = new JX.Dog(); * var bound_function = JX.bind(dog, dog.barkNow); * * JX.Stratcom.listen('click', 'bark', bound_function); * * ##bound_function## is a function with ##dog## bound as ##this##; ##this## * will always be ##dog## when the function is called, no matter what * property chain it is invoked from. * * You can also pass ##null## as the context argument to implicitly bind * ##window##. * * = Partial Function Application = * * @{JX.bind()} also performs partial function application, which allows you * to bind one or more arguments to a function. For instance, if we have a * simple function which adds two numbers: * * function add(a, b) { return a + b; } * add(3, 4); // 7 * * Suppose we want a new function, like this: * * function add3(b) { return 3 + b; } * add3(4); // 7 * * Instead of doing this, we can define ##add3()## in terms of ##add()## by * binding the value ##3## to the ##a## argument: * * var add3_bound = JX.bind(null, add, 3); * add3_bound(4); // 7 * * Zero or more arguments may be bound in this way. This is particularly useful * when using closures in a loop: * * COUNTEREXAMPLE * for (var ii = 0; ii < button_list.length; ii++) { * button_list[ii].onclick = function() { * JX.log('You clicked button number '+ii+'!'); // Fails! * }; * } * * This doesn't work; all the buttons report the highest number when clicked. * This is because the local ##ii## is captured by the closure. Instead, bind * the current value of ##ii##: * * var func = function(button_num) { * JX.log('You clicked button number '+button_num+'!'); * } * for (var ii = 0; ii < button_list.length; ii++) { * button_list[ii].onclick = JX.bind(null, func, ii); * } * * @param obj|null Context object to bind as ##this##. * @param function Function to bind context and arguments to. * @param ... Zero or more arguments to bind. * @return function New function which invokes the original function with * bound context and arguments when called. */ JX.bind = function(context, func, more) { if (__DEV__) { if (typeof func != 'function') { throw new Error( 'JX.bind(context, , ...): '+ 'Attempting to bind something that is not a function.'); } } var bound = JX.$A(arguments).slice(2); return function() { return func.apply(context || window, bound.concat(JX.$A(arguments))); } }; /** * "Bag of holding"; function that does nothing. Primarily, it's used as a * placeholder when you want something to be callable but don't want it to * actually have an effect. * * @return void */ JX.bag = function() { // \o\ \o/ /o/ woo dance party }; /** * Convert an object's keys into a list. For example: * * JX.keys({sun: 1, moon: 1, stars: 1}); // Returns: ['sun', 'moon', 'stars'] * * @param obj Object to retrieve keys from. * @return list List of keys. */ JX.keys = function(obj) { var r = []; for (var k in obj) { r.push(k); } return r; }; /** * Defer a function for later execution, similar to ##setTimeout()##. Returns * an object with a ##stop()## method, which cancels the deferred call. * * var ref = JX.defer(yell, 3000); // Yell in 3 seconds. * // ... * ref.stop(); // Cancel the yell. * * @param function Function to invoke after the timeout. * @param int? Timeout, in milliseconds. If this value is omitted, the * function will be invoked once control returns to the browser * event loop, as with ##setTimeout(func, 0)##. * @return obj An object with a ##stop()## method, which cancels function * execution. */ JX.defer = function(func, timeout) { var t = setTimeout(func, timeout || 0); return {stop : function() { clearTimeout(t); }} }; JX.id = function(any) { return any; }; if (__DEV__) { if (!window.console || !window.console.log) { if (window.opera && window.opera.postError) { window.console = {log: function(m) { window.opera.postError(m); }}; } else { window.console = {log: function(m) { }}; } } /** * Print a message to the browser debugging console (like Firebug). This * method exists only in ##__DEV__##. * * @param string Message to print to the browser debugging console. * @return void */ JX.log = function(message) { window.console.log(message); } window.alert = (function(native_alert) { var recent_alerts = []; var in_alert = false; return function(msg) { if (in_alert) { JX.log( 'alert(...): '+ 'discarded reentrant alert.'); return; } in_alert = true; recent_alerts.push(new Date().getTime()); if (recent_alerts.length > 3) { recent_alerts.splice(0, recent_alerts.length - 3); } if (recent_alerts.length >= 3 && (recent_alerts[recent_alerts.length - 1] - recent_alerts[0]) < 5000) { if (confirm(msg + "\n\nLots of alert()s recently. Kill them?")) { window.alert = JX.bag; } } else { // Note that we can't .apply() the IE6 version of this "function". native_alert(msg); } in_alert = false; } })(window.alert); } diff --git a/src/lib/DOM.js b/src/lib/DOM.js index 2730488..6fe8f24 100644 --- a/src/lib/DOM.js +++ b/src/lib/DOM.js @@ -1,776 +1,780 @@ /** - * @requires javelin-install javelin-util javelin-vector javelin-stratcom + * @requires javelin-magical-init + * javelin-install + * javelin-util + * javelin-vector + * javelin-stratcom * @provides javelin-dom * * @javelin-installs JX.$ * @javelin-installs JX.$N * @javelin-installs JX.$H * * @javelin */ /** * Select an element by its "id" attribute, like ##document.getElementById()##. * For example: * * var node = JX.$('some_id'); * * This will select the node with the specified "id" attribute: * * LANG=HTML *
...
* * If the specified node does not exist, @{JX.$()} will throw ##JX.$.NotFound##. * For other ways to select nodes from the document, see @{JX.DOM.scry()} and * @{JX.DOM.find()}. * * @param string "id" attribute to select from the document. * @return Node Node with the specified "id" attribute. */ JX.$ = function(id) { if (__DEV__) { if (!id) { throw new Error('Empty ID passed to JX.$()!'); } } var node = document.getElementById(id); if (!node || (node.id != id)) { if (__DEV__) { if (node && (node.id != id)) { throw new Error( 'JX.$("'+id+'"): '+ 'document.getElementById() returned an element without the '+ 'correct ID. This usually means that the element you are trying '+ 'to select is being masked by a form with the same value in its '+ '"name" attribute.'); } } throw JX.$.NotFound; } return node; }; JX.$.NotFound = {}; if (__DEV__) { // If we're in dev, upgrade this object into an Error so that it will // print something useful if it escapes the stack after being thrown. JX.$.NotFound = new Error( 'JX.$() or JX.DOM.find() call matched no nodes.'); } /** * Upcast a string into an HTML object so it is treated as markup instead of * plain text. See @{JX.$N} for discussion of Javelin's security model. Every * time you call this function you potentially open up a security hole. Avoid * its use wherever possible. * * This class intentionally supports only a subset of HTML because many browsers * named "Internet Explorer" have awkward restrictions around what they'll * accept for conversion to document fragments. Alter your datasource to emit * valid HTML within this subset if you run into an unsupported edge case. All * the edge cases are crazy and you should always be reasonably able to emit * a cohesive tag instead of an unappendable fragment. * * You may use @{JX.$H} as a shortcut for creating new JX.HTML instances. * * @task build String into HTML * @task nodes HTML into Nodes */ JX.install('HTML', { construct : function(str) { if (__DEV__) { var tags = ['legend', 'thead', 'tbody', 'tfoot', 'column', 'colgroup', 'caption', 'tr', 'th', 'td', 'option']; var evil_stuff = new RegExp('^\\s*<(' + tags.join('|') + ')\\b', 'i'); var match = null; if (match = str.match(evil_stuff)) { throw new Error( 'new JX.HTML("<' + match[1] + '>..."): ' + 'call initializes an HTML object with an invalid partial fragment ' + 'and can not be converted into DOM nodes. The enclosing tag of an ' + 'HTML content string must be appendable to a document fragment. ' + 'For example, is allowed but or are not.'); } var really_evil = /..."): ' + 'call initializes an HTML object with an embedded script tag! ' + 'Are you crazy?! Do NOT do this!!!'); } var wont_work = /..."): ' + 'call initializes an HTML object with an embedded tag. IE ' + 'will not do the right thing with this.'); } // TODO(epriestley): May need to deny