Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2894113
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
147 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/webroot/rsrc/externals/javelin/core/__tests__/stratcom.js b/webroot/rsrc/externals/javelin/core/__tests__/stratcom.js
index 6638ef1fc0..2bdbae8d78 100644
--- a/webroot/rsrc/externals/javelin/core/__tests__/stratcom.js
+++ b/webroot/rsrc/externals/javelin/core/__tests__/stratcom.js
@@ -1,184 +1,184 @@
/**
* @requires javelin-stratcom
* javelin-dom
*/
describe('Stratcom Tests', function() {
- node1 = document.createElement('div');
+ var node1 = document.createElement('div');
JX.Stratcom.addSigil(node1, 'what');
- node2 = document;
- node3 = document.createElement('div');
+ var node2 = document;
+ var node3 = document.createElement('div');
node3.className = 'what';
it('should disallow document', function() {
ensure__DEV__(true, function() {
expect(function() {
JX.Stratcom.listen('click', 'tag:#document', function() {});
}).toThrow();
});
});
it('should disallow window', function() {
ensure__DEV__(true, function() {
expect(function() {
JX.Stratcom.listen('click', 'tag:window', function() {});
}).toThrow();
});
});
it('should test nodes for hasSigil', function() {
expect(JX.Stratcom.hasSigil(node1, 'what')).toBe(true);
expect(JX.Stratcom.hasSigil(node3, 'what')).toBe(false);
ensure__DEV__(true, function() {
expect(function() {
JX.Stratcom.hasSigil(node2, 'what');
}).toThrow();
});
});
it('should be able to add sigils', function() {
var node = document.createElement('div');
JX.Stratcom.addSigil(node, 'my-sigil');
expect(JX.Stratcom.hasSigil(node, 'my-sigil')).toBe(true);
expect(JX.Stratcom.hasSigil(node, 'i-dont-haz')).toBe(false);
JX.Stratcom.addSigil(node, 'javelin-rocks');
expect(JX.Stratcom.hasSigil(node, 'my-sigil')).toBe(true);
expect(JX.Stratcom.hasSigil(node, 'javelin-rocks')).toBe(true);
// Should not arbitrarily take away other sigils
JX.Stratcom.addSigil(node, 'javelin-rocks');
expect(JX.Stratcom.hasSigil(node, 'my-sigil')).toBe(true);
expect(JX.Stratcom.hasSigil(node, 'javelin-rocks')).toBe(true);
});
it('should test dataPersistence', function() {
var n, d;
n = JX.$N('div');
d = JX.Stratcom.getData(n);
expect(d).toEqual({});
d.noise = 'quack';
expect(JX.Stratcom.getData(n).noise).toEqual('quack');
n = JX.$N('div');
JX.Stratcom.addSigil(n, 'oink');
d = JX.Stratcom.getData(n);
expect(JX.Stratcom.getData(n)).toEqual({});
d.noise = 'quack';
expect(JX.Stratcom.getData(n).noise).toEqual('quack');
ensure__DEV__(true, function(){
var bad_values = [false, null, undefined, 'quack'];
for (var ii = 0; ii < bad_values.length; ii++) {
n = JX.$N('div');
expect(function() {
JX.Stratcom.addSigil(n, 'oink');
JX.Stratcom.addData(n, bad_values[ii]);
}).toThrow();
}
});
});
it('should allow the merge of additional data', function() {
ensure__DEV__(true, function() {
var clown = JX.$N('div');
clown.setAttribute('data-meta', '0_0');
JX.Stratcom.mergeData('0', {'0' : 'clown'});
expect(JX.Stratcom.getData(clown)).toEqual('clown');
var town = JX.$N('div');
town.setAttribute('data-meta', '0_1');
JX.Stratcom.mergeData('0', {'1' : 'town'});
expect(JX.Stratcom.getData(clown)).toEqual('clown');
expect(JX.Stratcom.getData(town)).toEqual('town');
expect(function() {
JX.Stratcom.mergeData('0', {'0' : 'oops'});
}).toThrow();
});
});
it('all listeners should be called', function() {
ensure__DEV__(true, function() {
var callback_count = 0;
JX.Stratcom.listen('custom:eventA', null, function() {
callback_count++;
});
JX.Stratcom.listen('custom:eventA', null, function() {
callback_count++;
});
expect(callback_count).toEqual(0);
JX.Stratcom.invoke('custom:eventA');
expect(callback_count).toEqual(2);
});
});
it('removed listeners should not be called', function() {
ensure__DEV__(true, function() {
var callback_count = 0;
var listeners = [];
var remove_listeners = function() {
while (listeners.length) {
listeners.pop().remove();
}
};
listeners.push(
JX.Stratcom.listen('custom:eventB', null, function() {
callback_count++;
remove_listeners();
})
);
listeners.push(
JX.Stratcom.listen('custom:eventB', null, function() {
callback_count++;
remove_listeners();
})
);
expect(callback_count).toEqual(0);
JX.Stratcom.invoke('custom:eventB');
expect(listeners.length).toEqual(0);
expect(callback_count).toEqual(1);
});
});
it('should throw when accessing data in an unloaded block', function() {
ensure__DEV__(true, function() {
var n = JX.$N('div');
n.setAttribute('data-meta', '9999999_9999999');
var caught;
try {
JX.Stratcom.getData(n);
} catch (error) {
caught = error;
}
expect(caught instanceof Error).toEqual(true);
});
});
// it('can set data serializer', function() {
// var uri = new JX.URI('http://www.facebook.com/home.php?key=value');
// uri.setQuerySerializer(JX.PHPQuerySerializer.serialize);
// uri.setQueryParam('obj', {
// num : 1,
// obj : {
// str : 'abc',
// i : 123
// }
// });
// expect(decodeURIComponent(uri.toString())).toEqual(
// 'http://www.facebook.com/home.php?key=value&' +
// 'obj[num]=1&obj[obj][str]=abc&obj[obj][i]=123');
// });
});
diff --git a/webroot/rsrc/externals/javelin/core/__tests__/util.js b/webroot/rsrc/externals/javelin/core/__tests__/util.js
index b2f72c6747..0f7f861a19 100644
--- a/webroot/rsrc/externals/javelin/core/__tests__/util.js
+++ b/webroot/rsrc/externals/javelin/core/__tests__/util.js
@@ -1,85 +1,85 @@
/**
* @requires javelin-util
*/
describe('JX.isArray', function() {
it('should correctly identify an array', function() {
expect(JX.isArray([1, 2, 3])).toBe(true);
expect(JX.isArray([])).toBe(true);
});
it('should return false on anything that is not an array', function() {
expect(JX.isArray(1)).toBe(false);
expect(JX.isArray('a string')).toBe(false);
expect(JX.isArray(true)).toBe(false);
expect(JX.isArray(/regex/)).toBe(false);
expect(JX.isArray(new String('a super string'))).toBe(false);
expect(JX.isArray(new Number(42))).toBe(false);
expect(JX.isArray(new Boolean(false))).toBe(false);
expect(JX.isArray({})).toBe(false);
expect(JX.isArray({'0': 1, '1': 2, length: 2})).toBe(false);
expect(JX.isArray((function(){
return arguments;
})('I', 'want', 'to', 'trick', 'you'))).toBe(false);
});
it('should identify an array from another context as an array', function() {
var iframe = document.createElement('iframe');
- var name = iframe.name = 'javelin-iframe-test';
+ iframe.name = 'javelin-iframe-test';
iframe.style.display = 'none';
document.body.insertBefore(iframe, document.body.firstChild);
var doc = iframe.contentWindow.document;
doc.write(
'<script>parent.MaybeArray = Array;</script>'
);
var array = MaybeArray(1, 2, 3);
var array2 = new MaybeArray(1);
array2[0] = 5;
expect(JX.isArray(array)).toBe(true);
expect(JX.isArray(array2)).toBe(true);
});
});
describe('JX.bind', function() {
it('should bind a function to a context', function() {
var object = {a: 5, b: 3};
JX.bind(object, function() {
object.b = 1;
})();
expect(object).toEqual({a: 5, b: 1});
});
it('should bind a function without context', function() {
var called;
JX.bind(null, function() {
called = true;
})();
expect(called).toBe(true);
});
it('should bind with arguments', function() {
var list = [];
JX.bind(null, function() {
list.push.apply(list, JX.$A(arguments));
}, 'a', 2, 'c', 4)();
expect(list).toEqual(['a', 2, 'c', 4]);
});
it('should allow to pass additional arguments', function() {
var list = [];
JX.bind(null, function() {
list.push.apply(list, JX.$A(arguments));
}, 'a', 2)('c', 4);
expect(list).toEqual(['a', 2, 'c', 4]);
});
});
diff --git a/webroot/rsrc/externals/javelin/core/install.js b/webroot/rsrc/externals/javelin/core/install.js
index 284d529be4..c56e6489a0 100644
--- a/webroot/rsrc/externals/javelin/core/install.js
+++ b/webroot/rsrc/externals/javelin/core/install.js
@@ -1,455 +1,455 @@
/**
* @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
*/
JX.install = function(new_name, new_junk) {
// If we've already installed this, something is up.
if (new_name in JX) {
if (__DEV__) {
JX.$E(
'JX.install("' + new_name + '", ...): ' +
'trying to reinstall something that has already been installed.');
}
return;
}
if (__DEV__) {
if ('name' in new_junk) {
JX.$E(
'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.
(JX.install._queue || (JX.install._queue = [])).push([new_name, new_junk]);
var name;
do {
var junk;
var initialize;
name = null;
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) {
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) {
var name = junk.name || '';
var k;
var ii;
if (__DEV__) {
var valid = {
construct : 1,
statics : 1,
members : 1,
extend : 1,
properties : 1,
events : 1,
name : 1
};
for (k in junk) {
if (!(k in valid)) {
JX.$E(
'JX.createClass("' + name + '", {"' + k + '": ...}): ' +
'trying to create unknown property `' + k + '`.');
}
}
if (junk.constructor !== {}.constructor) {
JX.$E(
'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) {
JX.$E(
'<' + 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 (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;
var setter = function(prop) {
return function(v) {
this[prop] = v;
return this;
};
};
var getter = function(prop) {
- return function(v) {
+ return function() {
return this[prop];
};
};
// Build getters and setters from the `prop' map.
for (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] = setter(prop);
proto['get' + base] = getter(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] == '_') {
JX.$E(
'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) {
JX.$E(
'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 (k in junk.members) {
proto[k] = junk.members[k];
}
// IE does not enumerate some properties on objects
var enumerables = JX.install._enumerables;
if (junk.members && enumerables) {
ii = enumerables.length;
while (ii--){
var property = enumerables[ii];
if (junk.members[property]) {
proto[property] = junk.members[property];
}
}
}
// 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 (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__)) {
JX.$E(
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__)) {
JX.$E(
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__)) {
JX.$E(
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() {
JX.$E(
this.__readable__ + '.listen(...): ' +
error_message);
};
Class.invoke = Class.invoke || function() {
JX.$E(
this.__readable__ + '.invoke(...): ' +
error_message);
};
proto.listen = proto.listen || function() {
JX.$E(
this.__class__.__readable__ + '.listen(...): ' +
error_message);
};
proto.invoke = proto.invoke || function() {
JX.$E(
this.__class__.__readable__ + '.invoke(...): ' +
error_message);
};
}
return Class;
};
JX.install._nextObjectID = 0;
JX.flushHoldingQueue('install', JX.install);
(function() {
// IE does not enter this loop.
for (var i in {toString: 1}) {
return;
}
JX.install._enumerables = [
'toString', 'hasOwnProperty', 'valueOf', 'isPrototypeOf',
'propertyIsEnumerable', 'toLocaleString', 'constructor'
];
})();
diff --git a/webroot/rsrc/externals/javelin/core/util.js b/webroot/rsrc/externals/javelin/core/util.js
index be43b73faa..e09171d0d6 100644
--- a/webroot/rsrc/externals/javelin/core/util.js
+++ b/webroot/rsrc/externals/javelin/core/util.js
@@ -1,345 +1,345 @@
/**
* Javelin utility functions.
*
* @provides javelin-util
*
* @javelin-installs JX.$E
* @javelin-installs JX.$A
* @javelin-installs JX.$AX
* @javelin-installs JX.isArray
* @javelin-installs JX.copy
* @javelin-installs JX.bind
* @javelin-installs JX.bag
* @javelin-installs JX.keys
* @javelin-installs JX.log
* @javelin-installs JX.id
* @javelin-installs JX.now
*
* @javelin
*/
/**
* Throw an exception and attach the caller data in the exception.
*
* @param string Exception message.
*/
JX.$E = function(message) {
var e = new Error(message);
var caller_fn = JX.$E.caller;
if (caller_fn) {
e.caller_fn = caller_fn.caller;
}
throw e;
};
/**
* 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(object) {
// IE8 throws "JScript object expected" when trying to call
// Array.prototype.slice on a NodeList, so just copy items one by one here.
var r = [];
for (var ii = 0; ii < object.length; ii++) {
r.push(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 a @{function:JX.isArray} check whether or not
* the argument is an array, so you may need to convert array-like objects (such
* as ##arguments##) into real arrays with @{function:JX.$A}.
*
* This function is mostly useful to create methods which accept either a
* value or a list of values.
*
* @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 JX.isArray(maybe_scalar) ? maybe_scalar : [maybe_scalar];
};
/**
* Checks whether a value is an array.
*
* JX.isArray(['an', 'array']); // Returns true.
* JX.isArray('Not an Array'); // Returns false.
*
* @param wild Any value.
* @return bool true if the argument is an array, false otherwise.
*/
JX.isArray = Array.isArray || function(maybe_array) {
return Object.prototype.toString.call(maybe_array) == '[object Array]';
};
/**
* Copy properties from one object to another. 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'
* // }
*
* NOTE: This function 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.
*
* @param obj Destination object, which properties should be copied to.
* @param obj Source object, which properties should be copied from.
* @return obj Modified 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; @{method: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 =
*
* @{function: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') {
JX.$E(
'JX.bind(context, <yuck>, ...): '+
'Attempting to bind something that is not a function.');
}
}
var bound = JX.$A(arguments).slice(2);
if (func.bind) {
return func.bind.apply(func, [context].concat(bound));
}
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 = Object.keys || function(obj) {
var r = [];
for (var k in obj) {
r.push(k);
}
return r;
};
/**
* Identity function; returns the argument unmodified. This is primarily useful
* as a placeholder for some callback which may transform its argument.
*
* @param wild Any value.
* @return wild The passed argument.
*/
JX.id = function(any) {
return any;
};
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) { }};
+ window.console = {log: function() {}};
}
}
/**
* Print a message to the browser debugging console (like Firebug).
*
* @param string Message to print to the browser debugging console.
* @return void
*/
JX.log = function(message) {
window.console.log(message);
};
if (__DEV__) {
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(JX.now());
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 (window.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);
}
/**
* Date.now is the fastest timestamp function, but isn't supported by every
* browser. This gives the fastest version the environment can support.
* The wrapper function makes the getTime call even slower, but benchmarking
* shows it to be a marginal perf loss. Considering how small of a perf
* difference this makes overall, it's not really a big deal. The primary
* reason for this is to avoid hacky "just think of the byte savings" JS
* like +new Date() that has an unclear outcome for the unexposed.
*
* @return Int A Unix timestamp of the current time on the local machine
*/
JX.now = (Date.now || function() { return new Date().getTime(); });
diff --git a/webroot/rsrc/externals/javelin/ext/reactor/core/Reactor.js b/webroot/rsrc/externals/javelin/ext/reactor/core/Reactor.js
index a40d864dc8..3af21b14b5 100644
--- a/webroot/rsrc/externals/javelin/ext/reactor/core/Reactor.js
+++ b/webroot/rsrc/externals/javelin/ext/reactor/core/Reactor.js
@@ -1,89 +1,89 @@
/**
* @provides javelin-reactor
* @requires javelin-install
* javelin-util
* @javelin
*/
JX.install('Reactor', {
statics : {
/**
* Return this value from a ReactorNode transformer to indicate that
* its listeners should not be activated.
*/
DoNotPropagate : {},
/**
* For internal use by the Reactor system.
*/
propagatePulse : function(start_pulse, start_node) {
var reverse_post_order =
JX.Reactor._postOrder(start_node).reverse();
start_node.primeValue(start_pulse);
for (var ix = 0; ix < reverse_post_order.length; ix++) {
var node = reverse_post_order[ix];
var pulse = node.getNextPulse();
if (pulse === JX.Reactor.DoNotPropagate) {
continue;
}
var next_pulse = node.getTransformer()(pulse);
var sends_to = node.getListeners();
for (var jx = 0; jx < sends_to.length; jx++) {
sends_to[jx].primeValue(next_pulse);
}
}
},
/**
* For internal use by the Reactor system.
*/
_postOrder : function(node, result, pending) {
- if (typeof result === "undefined") {
+ if (typeof result === 'undefined') {
result = [];
pending = {};
}
pending[node.getGraphID()] = true;
var nexts = node.getListeners();
for (var ix = 0; ix < nexts.length; ix++) {
var next = nexts[ix];
if (pending[next.getGraphID()]) {
continue;
}
JX.Reactor._postOrder(next, result, pending);
}
result.push(node);
return result;
},
// Helper for lift.
_valueNow : function(fn, dynvals) {
var values = [];
for (var ix = 0; ix < dynvals.length; ix++) {
values.push(dynvals[ix].getValueNow());
}
return fn.apply(null, values);
},
/**
* Lift a function over normal values to be a function over dynvals.
* @param fn A function expecting normal values
* @param dynvals Array of DynVals whose instaneous values will be passed
* to fn.
* @return A DynVal representing the changing value of fn applies to dynvals
* over time.
*/
lift : function(fn, dynvals) {
var valueNow = JX.bind(null, JX.Reactor._valueNow, fn, dynvals);
var streams = [];
for (var ix = 0; ix < dynvals.length; ix++) {
streams.push(dynvals[ix].getChanges());
}
var result = new JX['ReactorNode'](streams, valueNow);
return new JX['DynVal'](result, valueNow());
}
}
});
diff --git a/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js b/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js
index 02c60f9c41..c500ce4c33 100644
--- a/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js
+++ b/webroot/rsrc/externals/javelin/ext/reactor/core/ReactorNode.js
@@ -1,96 +1,96 @@
/**
* @provides javelin-reactornode
* @requires javelin-install
* javelin-reactor
* javelin-util
* javelin-reactor-node-calmer
* @javelin
*/
JX.install('ReactorNode', {
members : {
_transformer : null,
_sendsTo : null,
_nextPulse : null,
_graphID : null,
getGraphID : function() {
return this._graphID || this.__id__;
},
setGraphID : function(id) {
this._graphID = id;
return this;
},
setTransformer : function(fn) {
this._transformer = fn;
return this;
},
/**
* Set up dest as a listener to this.
*/
listen : function(dest) {
this._sendsTo[dest.__id__] = dest;
return { remove : JX.bind(null, this._removeListener, dest) };
},
/**
* Helper for listen.
*/
_removeListener : function(dest) {
delete this._sendsTo[dest.__id__];
},
/**
* For internal use by the Reactor system
*/
primeValue : function(value) {
this._nextPulse = value;
},
getListeners : function() {
var result = [];
for (var k in this._sendsTo) {
result.push(this._sendsTo[k]);
}
return result;
},
/**
* For internal use by the Reactor system
*/
- getNextPulse : function(pulse) {
+ getNextPulse : function() {
return this._nextPulse;
},
getTransformer : function() {
return this._transformer;
},
forceSendValue : function(pulse) {
JX.Reactor.propagatePulse(pulse, this);
},
// fn should return JX.Reactor.DoNotPropagate to indicate a value that
// should not be retransmitted.
transform : function(fn) {
return new JX.ReactorNode([this], fn);
},
/**
* Suppress events to happen at most once per min_interval.
* The last event that fires within an interval will fire at the end
* of the interval. Events that are sandwiched between other events
* within an interval are dropped.
*/
calm : function(min_interval) {
var result = new JX.ReactorNode([this], JX.id);
var transformer = new JX.ReactorNodeCalmer(result, min_interval);
result.setTransformer(JX.bind(transformer, transformer.onPulse));
return result;
}
},
construct : function(source_streams, transformer) {
this._nextPulse = JX.Reactor.DoNotPropagate;
this._transformer = transformer;
this._sendsTo = {};
for (var ix = 0; ix < source_streams.length; ix++) {
source_streams[ix].listen(this);
}
}
});
diff --git a/webroot/rsrc/externals/javelin/ext/reactor/dom/RDOM.js b/webroot/rsrc/externals/javelin/ext/reactor/dom/RDOM.js
index f34907f27f..8bdbefd07b 100644
--- a/webroot/rsrc/externals/javelin/ext/reactor/dom/RDOM.js
+++ b/webroot/rsrc/externals/javelin/ext/reactor/dom/RDOM.js
@@ -1,405 +1,405 @@
/**
* Javelin Reactive functions to work with the DOM.
* @provides javelin-reactor-dom
* @requires javelin-dom
* javelin-dynval
* javelin-reactor
* javelin-reactornode
* javelin-install
* javelin-util
* @javelin
*/
JX.install('RDOM', {
statics : {
_time : null,
/**
* DynVal of the current time in milliseconds.
*/
time : function() {
if (JX.RDOM._time === null) {
var time = new JX.ReactorNode([], JX.id);
window.setInterval(function() {
time.forceSendValue(JX.now());
}, 100);
JX.RDOM._time = new JX.DynVal(time, JX.now());
}
return JX.RDOM._time;
},
/**
* Given a DynVal[String], return a DOM text node whose value tracks it.
*/
$DT : function(dyn_string) {
var node = document.createTextNode(dyn_string.getValueNow());
dyn_string.transform(function(s) { node.data = s; });
return node;
},
_recvEventPulses : function(node, event) {
var reactor_node = new JX.ReactorNode([], JX.id);
var no_path = null;
JX.DOM.listen(
node,
event,
no_path,
JX.bind(reactor_node, reactor_node.forceSendValue)
);
reactor_node.setGraphID(JX.DOM.uniqID(node));
return reactor_node;
},
_recvChangePulses : function(node) {
return JX.RDOM._recvEventPulses(node, 'change').transform(function() {
return node.value;
});
},
/**
* Sets up a bidirectional DynVal for a node.
* @param node :: DOM Node
* @param inPulsesFn :: DOM Node -> ReactorNode
* @param inDynValFn :: DOM Node -> ReactorNode -> DynVal
* @param outFn :: ReactorNode -> DOM Node
*/
_bidi : function(node, inPulsesFn, inDynValFn, outFn) {
var inPulses = inPulsesFn(node);
var inDynVal = inDynValFn(node, inPulses);
outFn(inDynVal.getChanges(), node);
inDynVal.getChanges().listen(inPulses);
return inDynVal;
},
/**
* ReactorNode[String] of the incoming values of a radio group.
* @param Array of DOM elements, all the radio buttons in a group.
*/
_recvRadioPulses : function(buttons) {
var ins = [];
for (var ii = 0; ii < buttons.length; ii++) {
ins.push(JX.RDOM._recvChangePulses(buttons[ii]));
}
return new JX.ReactorNode(ins, JX.id);
},
/**
* DynVal[String] of the incoming values of a radio group.
* pulses is a ReactorNode[String] of the incoming values of the group
*/
_recvRadio : function(buttons, pulses) {
var init = '';
for (var ii = 0; ii < buttons.length; ii++) {
if (buttons[ii].checked) {
init = buttons[ii].value;
break;
}
}
return new JX.DynVal(pulses, init);
},
/**
* Send the pulses from the ReactorNode[String] to the radio group.
* Sending an invalid value will result in a log message in __DEV__.
*/
_sendRadioPulses : function(rnode, buttons) {
return rnode.transform(function(val) {
var found;
if (__DEV__) {
found = false;
}
for (var ii = 0; ii < buttons.length; ii++) {
if (buttons[ii].value == val) {
buttons[ii].checked = true;
if (__DEV__) {
found = true;
}
}
}
if (__DEV__) {
if (!found) {
- throw new Error("Mismatched radio button value");
+ throw new Error('Mismatched radio button value');
}
}
});
},
/**
* Bidirectional DynVal[String] for a radio group.
* Sending an invalid value will result in a log message in __DEV__.
*/
radio : function(input) {
return JX.RDOM._bidi(
input,
JX.RDOM._recvRadioPulses,
JX.RDOM._recvRadio,
JX.RDOM._sendRadioPulses
);
},
/**
* ReactorNode[Boolean] of the values of the checkbox when it changes.
*/
_recvCheckboxPulses : function(checkbox) {
return JX.RDOM._recvChangePulses(checkbox).transform(function(val) {
return Boolean(val);
});
},
/**
* DynVal[Boolean] of the value of a checkbox.
*/
_recvCheckbox : function(checkbox, pulses) {
return new JX.DynVal(pulses, Boolean(checkbox.checked));
},
/**
* Send the pulses from the ReactorNode[Boolean] to the checkbox
*/
_sendCheckboxPulses : function(rnode, checkbox) {
return rnode.transform(function(val) {
if (__DEV__) {
if (!(val === true || val === false)) {
- throw new Error("Send boolean values to checkboxes.");
+ throw new Error('Send boolean values to checkboxes.');
}
}
checkbox.checked = val;
});
},
/**
* Bidirectional DynVal[Boolean] for a checkbox.
*/
checkbox : function(input) {
return JX.RDOM._bidi(
input,
JX.RDOM._recvCheckboxPulses,
JX.RDOM._recvCheckbox,
JX.RDOM._sendCheckboxPulses
);
},
/**
* ReactorNode[String] of the changing values of a text input.
*/
_recvInputPulses : function(input) {
// This misses advanced changes like paste events.
var live_changes = [
JX.RDOM._recvChangePulses(input),
JX.RDOM._recvEventPulses(input, 'keyup'),
JX.RDOM._recvEventPulses(input, 'keypress'),
JX.RDOM._recvEventPulses(input, 'keydown')
];
return new JX.ReactorNode(live_changes, function() {
return input.value;
});
},
/**
* DynVal[String] of the value of a text input.
*/
_recvInput : function(input, pulses) {
return new JX.DynVal(pulses, input.value);
},
/**
* Send the pulses from the ReactorNode[String] to the input
*/
_sendInputPulses : function(rnode, input) {
var result = rnode.transform(function(val) {
input.value = val;
});
result.setGraphID(JX.DOM.uniqID(input));
return result;
},
/**
* Bidirectional DynVal[String] for a text input.
*/
input : function(input) {
return JX.RDOM._bidi(
input,
JX.RDOM._recvInputPulses,
JX.RDOM._recvInput,
JX.RDOM._sendInputPulses
);
},
/**
* ReactorNode[String] of the incoming changes in value of a select element.
*/
_recvSelectPulses : function(select) {
return JX.RDOM._recvChangePulses(select);
},
/**
* DynVal[String] of the value of a select element.
*/
_recvSelect : function(select, pulses) {
return new JX.DynVal(pulses, select.value);
},
/**
* Send the pulses from the ReactorNode[String] to the select.
* Sending an invalid value will result in a log message in __DEV__.
*/
_sendSelectPulses : function(rnode, select) {
return rnode.transform(function(val) {
select.value = val;
if (__DEV__) {
if (select.value !== val) {
- throw new Error("Mismatched select value");
+ throw new Error('Mismatched select value');
}
}
});
},
/**
* Bidirectional DynVal[String] for the value of a select.
*/
select : function(select) {
return JX.RDOM._bidi(
select,
JX.RDOM._recvSelectPulses,
JX.RDOM._recvSelect,
JX.RDOM._sendSelectPulses
);
},
/**
* ReactorNode[undefined] that fires when a button is clicked.
*/
clickPulses : function(button) {
return JX.RDOM._recvEventPulses(button, 'click').transform(function() {
return null;
});
},
/**
* ReactorNode[Boolean] of whether the mouse is over a target.
*/
_recvIsMouseOverPulses : function(target) {
var mouseovers = JX.RDOM._recvEventPulses(target, 'mouseover').transform(
function() {
return true;
});
var mouseouts = JX.RDOM._recvEventPulses(target, 'mouseout').transform(
function() {
return false;
});
return new JX.ReactorNode([mouseovers, mouseouts], JX.id);
},
/**
* DynVal[Boolean] of whether the mouse is over a target.
*/
isMouseOver : function(target) {
// Not worth it to initialize this properly.
return new JX.DynVal(JX.RDOM._recvIsMouseOverPulses(target), false);
},
/**
* ReactorNode[Boolean] of whether an element has the focus.
*/
_recvHasFocusPulses : function(target) {
var focuses = JX.RDOM._recvEventPulses(target, 'focus').transform(
function() {
return true;
});
var blurs = JX.RDOM._recvEventPulses(target, 'blur').transform(
function() {
return false;
});
return new JX.ReactorNode([focuses, blurs], JX.id);
},
/**
* DynVal[Boolean] of whether an element has the focus.
*/
_recvHasFocus : function(target) {
var is_focused_now = (target === document.activeElement);
return new JX.DynVal(JX.RDOM._recvHasFocusPulses(target), is_focused_now);
},
_sendHasFocusPulses : function(rnode, target) {
rnode.transform(function(should_focus) {
if (should_focus) {
target.focus();
} else {
target.blur();
}
return should_focus;
});
},
/**
* Bidirectional DynVal[Boolean] of whether an element has the focus.
*/
hasFocus : function(target) {
return JX.RDOM._bidi(
target,
JX.RDOM._recvHasFocusPulses,
JX.RDOM._recvHasFocus,
JX.RDOM._sendHasFocusPulses
);
},
/**
* Send a CSS class from a DynVal to a node
*/
sendClass : function(dynval, node, className) {
return dynval.transform(function(add) {
JX.DOM.alterClass(node, className, add);
});
},
/**
* Dynamically attach a set of DynVals to a DOM node's properties as
* specified by props.
* props: {left: someDynVal, style: {backgroundColor: someOtherDynVal}}
*/
sendProps : function(node, props) {
var dynvals = [];
var keys = [];
var style_keys = [];
for (var key in props) {
keys.push(key);
if (key === 'style') {
for (var style_key in props[key]) {
style_keys.push(style_key);
dynvals.push(props[key][style_key]);
node.style[style_key] = props[key][style_key].getValueNow();
}
} else {
dynvals.push(props[key]);
node[key] = props[key].getValueNow();
}
}
return JX.Reactor.lift(JX.bind(null, function(keys, style_keys, node) {
var args = JX.$A(arguments).slice(3);
for (var ii = 0; ii < args.length; ii++) {
if (keys[ii] === 'style') {
for (var jj = 0; jj < style_keys.length; jj++) {
node.style[style_keys[jj]] = args[ii];
ii++;
}
ii--;
} else {
node[keys[ii]] = args[ii];
}
}
}, keys, style_keys, node), dynvals);
}
}
});
diff --git a/webroot/rsrc/externals/javelin/ext/view/HTMLView.js b/webroot/rsrc/externals/javelin/ext/view/HTMLView.js
index 244b252e05..c9eff564b8 100644
--- a/webroot/rsrc/externals/javelin/ext/view/HTMLView.js
+++ b/webroot/rsrc/externals/javelin/ext/view/HTMLView.js
@@ -1,137 +1,137 @@
/**
* Dumb HTML views. Mostly to demonstrate how the visitor pattern over these
* views works, as driven by validation. I'm not convinced it's actually a good
* idea to do validation.
*
* @provides javelin-view-html
* @requires javelin-install
* javelin-dom
* javelin-view-visitor
* javelin-util
*/
JX.install('HTMLView', {
extend: 'View',
members : {
render: function(rendered_children) {
return JX.$N(this.getName(), this.getAllAttributes(), rendered_children);
},
validate: function() {
this.accept(JX.HTMLView.getValidatingVisitor());
}
},
statics: {
getValidatingVisitor: function() {
return new JX.ViewVisitor(JX.HTMLView.validate);
},
- validate: function(view, children) {
+ validate: function(view) {
var spec = this._getHTMLSpec();
if (!(view.getName() in spec)) {
- throw new Error("invalid tag");
+ throw new Error('invalid tag');
}
var tag_spec = spec[view.getName()];
var attrs = view.getAllAttributes();
for (var attr in attrs) {
if (!(attr in tag_spec)) {
- throw new Error("invalid attr");
+ throw new Error('invalid attr');
}
var validator = tag_spec[attr];
- if (typeof validator === "function") {
+ if (typeof validator === 'function') {
return validator(attrs[attr]);
}
}
return true;
},
_validateRel: function(target) {
return target in {
- "_blank": 1,
- "_self": 1,
- "_parent": 1,
- "_top": 1
+ '_blank': 1,
+ '_self': 1,
+ '_parent': 1,
+ '_top': 1
};
},
_getHTMLSpec: function() {
var attrs_any_can_have = {
className: 1,
id: 1,
sigil: 1
};
var form_elem_attrs = {
name: 1,
value: 1
};
var spec = {
a: { href: 1, target: JX.HTMLView._validateRel },
b: {},
blockquote: {},
br: {},
button: JX.copy({}, form_elem_attrs),
canvas: {},
code: {},
dd: {},
div: {},
dl: {},
dt: {},
em: {},
embed: {},
fieldset: {},
form: { type: 1 },
h1: {},
h2: {},
h3: {},
h4: {},
h5: {},
h6: {},
hr: {},
i: {},
iframe: { src: 1 },
img: { src: 1, alt: 1 },
input: JX.copy({}, form_elem_attrs),
label: {'for': 1},
li: {},
ol: {},
optgroup: {},
option: JX.copy({}, form_elem_attrs),
p: {},
pre: {},
q: {},
select: {},
span: {},
strong: {},
sub: {},
sup: {},
table: {},
tbody: {},
td: {},
textarea: {},
tfoot: {},
th: {},
thead: {},
tr: {},
ul: {}
};
for (var k in spec) {
JX.copy(spec[k], attrs_any_can_have);
}
return spec;
},
registerToInterpreter: function(view_interpreter) {
var spec = this._getHTMLSpec();
for (var tag in spec) {
view_interpreter.register(tag, JX.HTMLView);
}
return view_interpreter;
}
}
});
diff --git a/webroot/rsrc/externals/javelin/ext/view/ViewInterpreter.js b/webroot/rsrc/externals/javelin/ext/view/ViewInterpreter.js
index 743071e101..bb2a5b0bc7 100644
--- a/webroot/rsrc/externals/javelin/ext/view/ViewInterpreter.js
+++ b/webroot/rsrc/externals/javelin/ext/view/ViewInterpreter.js
@@ -1,71 +1,71 @@
/**
* Experimental interpreter for nice views.
* This is CoffeeScript:
*
* d = declare
* selectable: false
* boxOrientation: Orientation.HORIZONTAL
* additionalClasses: ['some-css-class']
* MultiAvatar ref: 'avatars'
* div
* flex: 1
* div(
* span className: 'some-css-class', ref: 'actorTargetLine'
* span className: 'message-css', ref: 'message'
* )
*
* div
* boxOrientation: Orientation.HORIZONTAL
* className: 'attachment-css-class'
* div
* className: 'attachment-image-css-class'
* ref: 'attachmentImageContainer'
* boxOrientation: Orientation.HORIZONTAL
* div className: 'inline attachment-text', ref: 'attachmentText',
* div
* className: 'attachment-title'
* ref: 'attachmentTitle'
* flex: 1
* div
* className: 'attachment-subtitle'
* ref: 'attachmentSubtitle'
* flex: 1
* div className: 'clear'
* MiniUfi ref: 'miniUfi'
* FeedbackFlyout ref: 'feedbackFlyout'
*
* It renders to nested function calls of the form:
* view({....options...}, child1, child2, ...);
*
* This view interpreter is meant to make it work.
*
* @provides javelin-view-interpreter
* @requires javelin-view
* javelin-install
* javelin-dom
*/
JX.install('ViewInterpreter', {
members : {
register : function(name, view_cls) {
this[name] = function(/* [properties, ]children... */) {
var properties = arguments[0] || {};
var children = Array.prototype.slice.call(arguments, 1);
// Passing properties is optional
if (properties instanceof JX.View ||
properties instanceof JX.HTML ||
properties.nodeType ||
- typeof properties === "string") {
+ typeof properties === 'string') {
children.unshift(properties);
properties = {};
}
var result = new view_cls(properties).setName(name);
result.addChildren(children);
return result;
};
}
}
});
diff --git a/webroot/rsrc/externals/javelin/ext/view/ViewPlaceholder.js b/webroot/rsrc/externals/javelin/ext/view/ViewPlaceholder.js
index 7a2b79cfc2..6439e5c796 100644
--- a/webroot/rsrc/externals/javelin/ext/view/ViewPlaceholder.js
+++ b/webroot/rsrc/externals/javelin/ext/view/ViewPlaceholder.js
@@ -1,105 +1,105 @@
/**
* Initialize a client-side view from the server. The main idea here is to
* give server-side views a way to integrate with client-side views.
*
* The idea is that a client-side view will have an accompanying
* thin server-side component. The server-side component renders a placeholder
* element in the document, and then it will invoke this behavior to initialize
* the view into the placeholder.
*
* Because server-side views may be nested, we need to add complexity to
* handle nesting properly.
*
* Assuming a server-side view design that looks like hierarchical views,
* we have to handle structures like
*
* <server:component>
* <client:component id="1">
* <server:component>
* <client:component id="2">
* </client:component>
* </server:component>
* </client:component>
* </server:component>
*
* This leads to a problem: Client component 1 needs to initialize the behavior
* with its children, which includes client component 2. So client component
* 2 must be rendered first. When client component 2 is rendered, it will also
* initialize a copy of this behavior. If behaviors are run in the order they
* are initialized, the child component will run before the parent, and its
* placeholder won't exist yet.
*
* To solve this problem, placeholder behaviors are initialized with the token
* of a containing view that must be rendered first (if any) and a token
* representing it for its own children to depend on. This means the server code
* is free to call initBehavior in any order.
*
* In Phabricator, AphrontJavelinView demonstrates how to handle this correctly.
*
* config: {
* id: Node id to replace.
* view: class of view, without the 'JX.' prefix.
* params: view parameters
* children: messy and loud, cute when drunk
* trigger_id: id of containing view that must be rendered first
* }
*
* @provides javelin-behavior-view-placeholder
* @requires javelin-behavior
* javelin-dom
* javelin-view-renderer
* javelin-install
*/
-JX.behavior('view-placeholder', function(config, statics) {
+JX.behavior('view-placeholder', function(config) {
JX.ViewPlaceholder.register(config.trigger_id, config.id, function() {
var replace = JX.$(config.id);
var children = config.children;
- if (typeof children === "string") {
+ if (typeof children === 'string') {
children = JX.$H(children);
}
var view = new JX[config.view](config.params, children);
var rendered = JX.ViewRenderer.render(view);
JX.DOM.replace(replace, rendered);
});
});
JX.install('ViewPlaceholder', {
statics: {
register: function(wait_on_token, token, cb) {
var ready_q = [];
var waiting;
if (!wait_on_token || wait_on_token in JX.ViewPlaceholder.ready) {
ready_q.push({token: token, cb: cb});
} else {
waiting = JX.ViewPlaceholder.waiting;
waiting[wait_on_token] = waiting[wait_on_token] || [];
waiting[wait_on_token].push({token: token, cb: cb});
}
while(ready_q.length) {
var ready = ready_q.shift();
waiting = JX.ViewPlaceholder.waiting[ready.token];
if (waiting) {
for (var ii = 0; ii < waiting.length; ii++) {
ready_q.push(waiting[ii]);
}
delete JX.ViewPlaceholder.waiting[ready.token];
}
ready.cb();
JX.ViewPlaceholder.ready[token] = true;
}
},
ready: {},
waiting: {}
}
});
diff --git a/webroot/rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js b/webroot/rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js
index 1e8729e5c4..2247611f31 100644
--- a/webroot/rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js
+++ b/webroot/rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js
@@ -1,25 +1,25 @@
/**
* @requires javelin-view-renderer
* javelin-view
*/
describe('JX.ViewRenderer', function() {
it('should render children then parent', function() {
var child_rendered = false;
var child_rendered_first = false;
var child = new JX.View({});
var parent = new JX.View({});
parent.addChild(child);
- child.render = function(_) {
+ child.render = function() {
child_rendered = true;
};
- parent.render = function(rendered_children) {
+ parent.render = function() {
child_rendered_first = child_rendered;
};
JX.ViewRenderer.render(parent);
expect(child_rendered_first).toBe(true);
});
});
diff --git a/webroot/rsrc/externals/javelin/lib/Leader.js b/webroot/rsrc/externals/javelin/lib/Leader.js
index f259d79105..d6a7ebd5fa 100644
--- a/webroot/rsrc/externals/javelin/lib/Leader.js
+++ b/webroot/rsrc/externals/javelin/lib/Leader.js
@@ -1,308 +1,307 @@
/**
* @requires javelin-install
* @provides javelin-leader
* @javelin
*/
/**
* Synchronize multiple tabs over LocalStorage.
*
* This class elects one tab as the "Leader". It remains the leader until it
* is closed.
*
* Tabs can conditionally call a function if they are the leader using
* @{method:callIfLeader}. This will trigger leader election, and call the
* function if the current tab is elected. This can be used to keep one
* websocket open across a group of tabs, or play a sound only once in response
* to a server state change.
*
* Tabs can broadcast messages to other tabs using @{method:broadcast}. Each
* message has an optional ID. When a tab receives multiple copies of a message
* with the same ID, copies after the first copy are discarded. This can be
* used in conjunction with @{method:callIfLeader} to allow multiple event
* responders to trigger a reaction to an event (like a sound) and ensure that
* it is played only once (not once for each notification), and by only one
* tab (not once for each tab).
*
* Finally, tabs can register a callback which will run if they become the
* leading tab, by listening for `onBecomeLeader`.
*/
JX.install('Leader', {
events: ['onBecomeLeader', 'onReceiveBroadcast'],
statics: {
_interval: null,
_broadcastKey: 'JX.Leader.broadcast',
_leaderKey: 'JX.Leader.id',
/**
* Tracks leadership state. Since leadership election is asynchronous,
* we can't expose this directly without inconsistent behavior.
*/
_isLeader: false,
/**
* Keeps track of message IDs we've seen, so we send each message only
* once.
*/
_seen: {},
/**
* Helps keep the list of seen message IDs from growing without bound.
*/
_seenList: [],
/**
* Elect a leader, triggering leadership callbacks if they are registered.
*/
start: function() {
var self = JX.Leader;
self.callIfLeader(JX.bag);
},
/**
* Call a method if this tab is the leader.
*
* This is asynchronous because leadership election is asynchronous. If
* the current tab is not the leader after any election takes place, the
* callback will not be invoked.
*/
callIfLeader: function(callback) {
JX.Leader._callIf(callback, JX.bag);
},
/**
* Call a method after leader election.
*
* This is asynchronous because leadership election is asynchronous. The
* callback will be invoked after election takes place.
*
* This method is useful if you want to invoke a callback no matter what,
* but the callback behavior depends on whether this is the leader or
* not.
*/
call: function(callback) {
JX.Leader._callIf(callback, callback);
},
/**
* Elect a leader, then invoke either a leader callback or a follower
* callback.
*/
_callIf: function(leader_callback, follower_callback) {
if (!window.localStorage) {
// If we don't have localStorage, pretend we're the only tab.
self._becomeLeader();
leader_callback();
return;
}
var self = JX.Leader;
// If we don't have an ID for this tab yet, generate one and register
// event listeners.
if (!self._id) {
self._id = 1 + parseInt(Math.random() * 1000000000, 10);
JX.Stratcom.listen('pagehide', null, self._pagehide);
JX.Stratcom.listen('storage', null, self._storage);
}
// Read the current leadership lease.
var lease = self._read();
// If the lease is good, we're all set.
var now = +new Date();
if (lease.until > now) {
if (lease.id === self._id) {
// If we haven't installed an update timer yet, do so now. This will
// renew our lease every 5 seconds, making sure we hold it until the
// tab is closed.
if (!self._interval && lease.until > now + 10000) {
self._interval = window.setInterval(self._write, 5000);
}
self._becomeLeader();
leader_callback();
} else {
follower_callback();
}
return;
}
// If the lease isn't good, try to become the leader. We don't have
// proper locking primitives for this, but can do a relatively good
// job. The algorithm here is:
//
// - Write our ID, trying to acquire the lease.
// - Delay for much longer than a write "could possibly" take.
// - Read the key back.
// - If nothing else overwrote the key, we become the leader.
//
// This avoids a race where our reads and writes could otherwise
// interleave with another tab's reads and writes, electing both or
// neither as the leader.
//
// This approximately follows an algorithm attributed to Fischer in
// "A Fast Mutual Exclusion Algorithm" (Leslie Lamport, 1985). That
// paper also describes a faster (but more complex) algorithm, but
// it's not problematic to add a significant delay here because
// leader election is not especially performance-sensitive.
self._write();
window.setTimeout(
JX.bind(null, self._callIf, leader_callback, follower_callback),
50);
},
/**
* Send a message to all open tabs.
*
* Tabs can receive messages by listening to `onReceiveBroadcast`.
*
* @param string|null Message ID. If provided, subsequent messages with
* the same ID will be discarded.
* @param wild The message to send.
*/
broadcast: function(id, message) {
var self = JX.Leader;
if (id !== null) {
if (id in self._seen) {
return;
}
self._markSeen(id);
}
if (window.localStorage) {
var json = JX.JSON.stringify(
{
id: id,
message: message,
// LocalStorage only emits events if the value changes. Include
// a random component to make sure that broadcasts are never
// eaten. Although this is probably not often useful in a
// production system, it makes testing easier and more predictable.
uniq: parseInt(Math.random() * 1000000, 10)
});
window.localStorage.setItem(self._broadcastKey, json);
}
self._receiveBroadcast(message);
},
/**
* Write a lease which names us as the leader.
*/
_write: function() {
var self = JX.Leader;
var str = [self._id, ((+new Date()) + 16000)].join(':');
window.localStorage.setItem(self._leaderKey, str);
},
/**
* Read the current lease.
*/
_read: function() {
var self = JX.Leader;
- leader = window.localStorage.getItem(self._leaderKey) || '0:0';
+ var leader = window.localStorage.getItem(self._leaderKey) || '0:0';
leader = leader.split(':');
return {
id: parseInt(leader[0], 10),
until: parseInt(leader[1], 10)
};
},
/**
* When the tab is closed, if we're the leader, release leadership.
*
* This will trigger a new election if there are other tabs open.
*/
_pagehide: function() {
var self = JX.Leader;
if (self._read().id === self._id) {
window.localStorage.removeItem(self._leaderKey);
}
},
/**
* React to a storage update.
*/
_storage: function(e) {
var self = JX.Leader;
var key = e.getRawEvent().key;
var new_value = e.getRawEvent().newValue;
switch (key) {
case self._broadcastKey:
new_value = JX.JSON.parse(new_value);
if (new_value.id !== null) {
if (new_value.id in self._seen) {
return;
}
self._markSeen(new_value.id);
}
self._receiveBroadcast(new_value.message);
break;
case self._leaderKey:
// If the leader tab closed, elect a new leader.
if (new_value === null) {
self.callIfLeader(JX.bag);
}
break;
}
},
_receiveBroadcast: function(message) {
var self = JX.Leader;
new JX.Leader().invoke('onReceiveBroadcast', message, self._isLeader);
},
_becomeLeader: function() {
var self = JX.Leader;
if (self._isLeader) {
return;
}
self._isLeader = true;
new JX.Leader().invoke('onBecomeLeader');
},
/**
* Mark a message as seen.
*
* We keep a fixed-sized list of recent messages, and let old ones fall
* off the end after a while.
*/
_markSeen: function(id) {
var self = JX.Leader;
self._seen[id] = true;
self._seenList.push(id);
while (self._seenList.length > 128) {
delete self._seen[self._seenList[0]];
self._seenList.splice(0, 1);
}
}
}
});
-
diff --git a/webroot/rsrc/externals/javelin/lib/Request.js b/webroot/rsrc/externals/javelin/lib/Request.js
index 14d13905d0..50e044b038 100644
--- a/webroot/rsrc/externals/javelin/lib/Request.js
+++ b/webroot/rsrc/externals/javelin/lib/Request.js
@@ -1,482 +1,482 @@
/**
* @requires javelin-install
* javelin-stratcom
* javelin-util
* javelin-behavior
* javelin-json
* javelin-dom
* javelin-resource
* javelin-routable
* @provides javelin-request
* @javelin
*/
/**
* Make basic AJAX XMLHTTPRequests.
*/
JX.install('Request', {
construct : function(uri, handler) {
this.setURI(uri);
if (handler) {
this.listen('done', handler);
}
},
events : ['start', 'open', 'send', 'statechange', 'done', 'error', 'finally',
'uploadprogress'],
members : {
_xhrkey : null,
_transport : null,
_sent : false,
_finished : false,
_block : null,
_data : null,
_getSameOriginTransport : function() {
try {
try {
return new XMLHttpRequest();
} catch (x) {
- return new ActiveXObject("Msxml2.XMLHTTP");
+ return new ActiveXObject('Msxml2.XMLHTTP');
}
} catch (x) {
- return new ActiveXObject("Microsoft.XMLHTTP");
+ return new ActiveXObject('Microsoft.XMLHTTP');
}
},
_getCORSTransport : function() {
try {
var xport = new XMLHttpRequest();
if ('withCredentials' in xport) {
// XHR supports CORS
} else if (typeof XDomainRequest != 'undefined') {
xport = new XDomainRequest();
}
return xport;
} catch (x) {
return new XDomainRequest();
}
},
getTransport : function() {
if (!this._transport) {
this._transport = this.getCORS() ? this._getCORSTransport() :
this._getSameOriginTransport();
}
return this._transport;
},
getRoutable: function() {
var routable = new JX.Routable();
routable.listen('start', JX.bind(this, function() {
// Pass the event to allow other listeners to "start" to configure this
// request before it fires.
JX.Stratcom.pass(JX.Stratcom.context());
this.send();
}));
this.listen('finally', JX.bind(routable, routable.done));
return routable;
},
send : function() {
if (this._sent || this._finished) {
if (__DEV__) {
if (this._sent) {
JX.$E(
'JX.Request.send(): ' +
'attempting to send a Request that has already been sent.');
}
if (this._finished) {
JX.$E(
'JX.Request.send(): ' +
'attempting to send a Request that has finished or aborted.');
}
}
return;
}
// Fire the "start" event before doing anything. A listener may
// perform pre-processing or validation on this request
this.invoke('start', this);
if (this._finished) {
return;
}
var xport = this.getTransport();
xport.onreadystatechange = JX.bind(this, this._onreadystatechange);
if (xport.upload) {
xport.upload.onprogress = JX.bind(this, this._onuploadprogress);
}
var method = this.getMethod().toUpperCase();
if (__DEV__) {
if (this.getRawData()) {
if (method != 'POST') {
JX.$E(
'JX.Request.send(): ' +
'attempting to send post data over GET. You must use POST.');
}
}
}
var list_of_pairs = this._data || [];
list_of_pairs.push(['__ajax__', true]);
this._block = JX.Stratcom.allocateMetadataBlock();
list_of_pairs.push(['__metablock__', this._block]);
var q = (this.getDataSerializer() ||
JX.Request.defaultDataSerializer)(list_of_pairs);
var uri = this.getURI();
// If we're sending a file, submit the metadata via the URI instead of
// via the request body, because the entire post body will be consumed by
// the file content.
if (method == 'GET' || this.getRawData()) {
uri += ((uri.indexOf('?') === -1) ? '?' : '&') + q;
}
if (this.getTimeout()) {
this._timer = setTimeout(
JX.bind(
this,
this._fail,
JX.Request.ERROR_TIMEOUT),
this.getTimeout());
}
xport.open(method, uri, true);
// Must happen after xport.open so that listeners can modify the transport
// Some transport properties can only be set after the transport is open
this.invoke('open', this);
if (this._finished) {
return;
}
this.invoke('send', this);
if (this._finished) {
return;
}
if (method == 'POST') {
if (this.getRawData()) {
xport.send(this.getRawData());
} else {
xport.setRequestHeader(
'Content-Type',
'application/x-www-form-urlencoded');
xport.send(q);
}
} else {
xport.send(null);
}
this._sent = true;
},
abort : function() {
this._cleanup();
},
_onuploadprogress : function(progress) {
this.invoke('uploadprogress', progress);
},
_onreadystatechange : function() {
var xport = this.getTransport();
var response;
try {
this.invoke('statechange', this);
if (this._finished) {
return;
}
if (xport.readyState != 4) {
return;
}
// XHR requests to 'file:///' domains return 0 for success, which is why
// we treat it as a good result in addition to HTTP 2XX responses.
if (xport.status !== 0 && (xport.status < 200 || xport.status >= 300)) {
this._fail();
return;
}
if (__DEV__) {
var expect_guard = this.getExpectCSRFGuard();
if (!xport.responseText.length) {
JX.$E(
'JX.Request("'+this.getURI()+'", ...): '+
'server returned an empty response.');
}
if (expect_guard && xport.responseText.indexOf('for (;;);') !== 0) {
JX.$E(
'JX.Request("'+this.getURI()+'", ...): '+
'server returned an invalid response.');
}
if (expect_guard && xport.responseText == 'for (;;);') {
JX.$E(
'JX.Request("'+this.getURI()+'", ...): '+
'server returned an empty response.');
}
}
response = this._extractResponse(xport);
if (!response) {
JX.$E(
'JX.Request("'+this.getURI()+'", ...): '+
'server returned an invalid response.');
}
} catch (exception) {
if (__DEV__) {
JX.log(
'JX.Request("'+this.getURI()+'", ...): '+
'caught exception processing response: '+exception);
}
this._fail();
return;
}
try {
this._handleResponse(response);
this._cleanup();
} catch (exception) {
// In Firefox+Firebug, at least, something eats these. :/
setTimeout(function() {
throw exception;
}, 0);
}
},
_extractResponse : function(xport) {
var text = xport.responseText;
if (this.getExpectCSRFGuard()) {
text = text.substring('for (;;);'.length);
}
var type = this.getResponseType().toUpperCase();
if (type == 'TEXT') {
return text;
} else if (type == 'JSON' || type == 'JAVELIN') {
return JX.JSON.parse(text);
} else if (type == 'XML') {
var doc;
try {
if (typeof DOMParser != 'undefined') {
var parser = new DOMParser();
- doc = parser.parseFromString(text, "text/xml");
+ doc = parser.parseFromString(text, 'text/xml');
} else { // IE
// an XDomainRequest
- doc = new ActiveXObject("Microsoft.XMLDOM");
+ doc = new ActiveXObject('Microsoft.XMLDOM');
doc.async = false;
doc.loadXML(xport.responseText);
}
return doc.documentElement;
} catch (exception) {
if (__DEV__) {
JX.log(
'JX.Request("'+this.getURI()+'", ...): '+
'caught exception extracting response: '+exception);
}
this._fail();
return null;
}
}
if (__DEV__) {
JX.$E(
'JX.Request("'+this.getURI()+'", ...): '+
'unrecognized response type.');
}
return null;
},
_fail : function(error) {
this._cleanup();
this.invoke('error', error, this);
this.invoke('finally');
},
_done : function(response) {
this._cleanup();
if (response.onload) {
for (var ii = 0; ii < response.onload.length; ii++) {
(new Function(response.onload[ii]))();
}
}
var payload;
if (this.getRaw()) {
payload = response;
} else {
payload = response.payload;
JX.Request._parseResponsePayload(payload);
}
this.invoke('done', payload, this);
this.invoke('finally');
},
_cleanup : function() {
this._finished = true;
clearTimeout(this._timer);
this._timer = null;
// Should not abort the transport request if it has already completed
// Otherwise, we may see an "HTTP request aborted" error in the console
// despite it possibly having succeeded.
if (this._transport && this._transport.readyState != 4) {
this._transport.abort();
}
},
setData : function(dictionary) {
this._data = null;
this.addData(dictionary);
return this;
},
addData : function(dictionary) {
if (!this._data) {
this._data = [];
}
for (var k in dictionary) {
this._data.push([k, dictionary[k]]);
}
return this;
},
setDataWithListOfPairs : function(list_of_pairs) {
this._data = list_of_pairs;
return this;
},
_handleResponse : function(response) {
if (this.getResponseType().toUpperCase() == 'JAVELIN') {
if (response.error) {
this._fail(response.error);
} else {
JX.Stratcom.mergeData(
this._block,
response.javelin_metadata || {});
var when_complete = JX.bind(this, function() {
this._done(response);
JX.initBehaviors(response.javelin_behaviors || {});
});
if (response.javelin_resources) {
JX.Resource.load(response.javelin_resources, when_complete);
} else {
when_complete();
}
}
} else {
this._cleanup();
this.invoke('done', response, this);
this.invoke('finally');
}
}
},
statics : {
ERROR_TIMEOUT : -9000,
defaultDataSerializer : function(list_of_pairs) {
var uri = [];
for (var ii = 0; ii < list_of_pairs.length; ii++) {
var pair = list_of_pairs[ii];
var name = encodeURIComponent(pair[0]);
var value = encodeURIComponent(pair[1]);
uri.push(name + '=' + value);
}
return uri.join('&');
},
/**
* When we receive a JSON blob, parse it to introduce meaningful objects
* where there are magic keys for placeholders.
*
* Objects with the magic key '__html' are translated into JX.HTML objects.
*
* This function destructively modifies its input.
*/
_parseResponsePayload: function(parent, index) {
var recurse = JX.Request._parseResponsePayload;
var obj = (typeof index !== 'undefined') ? parent[index] : parent;
if (JX.isArray(obj)) {
for (var ii = 0; ii < obj.length; ii++) {
recurse(obj, ii);
}
} else if (obj && typeof obj == 'object') {
if (('__html' in obj) && (obj.__html !== null)) {
parent[index] = JX.$H(obj.__html);
} else {
for (var key in obj) {
recurse(obj, key);
}
}
}
}
},
properties : {
URI : null,
dataSerializer : null,
/**
* Configure which HTTP method to use for the request. Permissible values
* are "POST" (default) or "GET".
*
* @param string HTTP method, one of "POST" or "GET".
*/
method : 'POST',
/**
* Set the data parameter of transport.send. Useful if you want to send a
* file or FormData. Not that you cannot send raw data and data at the same
* time.
*
* @param Data, argument to transport.send
*/
rawData: null,
raw : false,
/**
* Configure a timeout, in milliseconds. If the request has not resolved
* (either with success or with an error) within the provided timeframe,
* it will automatically fail with error JX.Request.ERROR_TIMEOUT.
*
* @param int Timeout, in milliseconds (e.g. 3000 = 3 seconds).
*/
timeout : null,
/**
* Whether or not we should expect the CSRF guard in the response.
*
* @param bool
*/
expectCSRFGuard : true,
/**
* Whether it should be a CORS (Cross-Origin Resource Sharing) request to
* a third party domain other than the current site.
*
* @param bool
*/
CORS : false,
/**
* Type of the response.
*
* @param enum 'JAVELIN', 'JSON', 'XML', 'TEXT'
*/
responseType : 'JAVELIN'
}
});
diff --git a/webroot/rsrc/externals/javelin/lib/Resource.js b/webroot/rsrc/externals/javelin/lib/Resource.js
index c5133ab6a8..4e7d528740 100644
--- a/webroot/rsrc/externals/javelin/lib/Resource.js
+++ b/webroot/rsrc/externals/javelin/lib/Resource.js
@@ -1,185 +1,185 @@
/**
* @provides javelin-resource
* @requires javelin-util
* javelin-uri
* javelin-install
*
* @javelin
*/
JX.install('Resource', {
statics: {
_loading: {},
_loaded: {},
_links: [],
_callbacks: [],
/**
* Loads one or many static resources (JavaScript or CSS) and executes a
* callback once these resources have finished loading.
*
* @param string|array static resource or list of resources to be loaded
* @param function callback when resources have finished loading
*/
load: function(list, callback) {
var resources = {},
- uri, resource, path, type;
+ uri, resource, path;
list = JX.$AX(list);
// In the event there are no resources to wait on, call the callback and
// exit. NOTE: it's better to do this check outside this function and not
// call through JX.Resource, but it's not always easy/possible to do so
if (!list.length) {
setTimeout(callback, 0);
return;
}
for (var ii = 0; ii < list.length; ii++) {
uri = new JX.URI(list[ii]);
resource = uri.toString();
path = uri.getPath();
resources[resource] = true;
if (JX.Resource._loaded[resource]) {
setTimeout(JX.bind(JX.Resource, JX.Resource._complete, resource), 0);
} else if (!JX.Resource._loading[resource]) {
JX.Resource._loading[resource] = true;
if (path.indexOf('.css') == path.length - 4) {
JX.Resource._loadCSS(resource);
} else {
JX.Resource._loadJS(resource);
}
}
}
JX.Resource._callbacks.push({
resources: resources,
callback: callback
});
},
_loadJS: function(uri) {
var script = document.createElement('script');
var load_callback = function() {
JX.Resource._complete(uri);
};
var error_callback = function() {
JX.$E('Resource: JS file download failure: ' + uri);
};
JX.copy(script, {
type: 'text/javascript',
src: uri
});
script.onload = load_callback;
script.onerror = error_callback;
script.onreadystatechange = function() {
var state = this.readyState;
if (state == 'complete' || state == 'loaded') {
load_callback();
}
};
document.getElementsByTagName('head')[0].appendChild(script);
},
_loadCSS: function(uri) {
var link = JX.copy(document.createElement('link'), {
type: 'text/css',
rel: 'stylesheet',
href: uri,
'data-href': uri // don't trust href
});
document.getElementsByTagName('head')[0].appendChild(link);
JX.Resource._links.push(link);
if (!JX.Resource._timer) {
JX.Resource._timer = setInterval(JX.Resource._poll, 20);
}
},
_poll: function() {
var sheets = document.styleSheets,
ii = sheets.length,
links = JX.Resource._links;
// Cross Origin CSS loading
// http://yearofmoo.com/2011/03/cross-browser-stylesheet-preloading/
while (ii--) {
var link = sheets[ii],
owner = link.ownerNode || link.owningElement,
jj = links.length;
if (owner) {
while (jj--) {
if (owner == links[jj]) {
JX.Resource._complete(links[jj]['data-href']);
links.splice(jj, 1);
}
}
}
}
if (!links.length) {
clearInterval(JX.Resource._timer);
JX.Resource._timer = null;
}
},
_complete: function(uri) {
var list = JX.Resource._callbacks,
current, ii;
delete JX.Resource._loading[uri];
JX.Resource._loaded[uri] = true;
var errors = [];
for (ii = 0; ii < list.length; ii++) {
current = list[ii];
delete current.resources[uri];
if (!JX.Resource._hasResources(current.resources)) {
try {
current.callback();
} catch (error) {
errors.push(error);
}
list.splice(ii--, 1);
}
}
if (errors.length) {
throw errors[0];
}
},
_hasResources: function(resources) {
for (var hasResources in resources) {
return true;
}
return false;
}
},
initialize: function() {
var list = JX.$A(document.getElementsByTagName('link')),
ii = list.length,
node;
while ((node = list[--ii])) {
if (node.type == 'text/css' && node.href) {
JX.Resource._loaded[(new JX.URI(node.href)).toString()] = true;
}
}
list = JX.$A(document.getElementsByTagName('script'));
ii = list.length;
while ((node = list[--ii])) {
if (node.type == 'text/javascript' && node.src) {
JX.Resource._loaded[(new JX.URI(node.src)).toString()] = true;
}
}
}
});
diff --git a/webroot/rsrc/externals/javelin/lib/WebSocket.js b/webroot/rsrc/externals/javelin/lib/WebSocket.js
index a7f5d1a864..8fd426f4bc 100644
--- a/webroot/rsrc/externals/javelin/lib/WebSocket.js
+++ b/webroot/rsrc/externals/javelin/lib/WebSocket.js
@@ -1,178 +1,178 @@
/**
* @requires javelin-install
* @provides javelin-websocket
* @javelin
*/
/**
* Wraps a WebSocket.
*/
JX.install('WebSocket', {
construct: function(uri) {
this.setURI(uri);
},
properties: {
URI: null,
/**
* Called when a connection is established or re-established after an
* interruption.
*/
openHandler: null,
/**
* Called when a message is received.
*/
messageHandler: null,
/**
* Called when the connection is closed.
*
* You can return `true` to prevent the socket from reconnecting.
*/
closeHandler: null
},
members: {
/**
* The underlying WebSocket.
*/
_socket: null,
/**
* Is the socket connected?
*/
_isOpen: false,
/**
* Has the caller asked us to close?
*
* By default, we reconnect when the connection is interrupted.
* This stops us from reconnecting if @{method:close} was called.
*/
_shouldClose: false,
/**
* Number of milliseconds to wait after a connection failure before
* attempting to reconnect.
*/
_delayUntilReconnect: null,
/**
* Open the connection.
*/
open: function() {
if (!window.WebSocket) {
return;
}
this._shouldClose = false;
this._resetDelay();
this._socket = new WebSocket(this.getURI());
this._socket.onopen = JX.bind(this, this._onopen);
this._socket.onmessage = JX.bind(this, this._onmessage);
this._socket.onclose = JX.bind(this, this._onclose);
},
/**
* Send a message.
*
* If the connection is not currently open, this method has no effect and
* the messages vanishes into the ether.
*/
send: function(message) {
if (this._isOpen) {
this._socket.send(message);
}
},
/**
* Close the connection.
*/
close: function() {
if (!this._isOpen) {
return;
}
this._shouldClose = true;
this._socket.close();
},
/**
* Callback for connection open.
*/
- _onopen: function(e) {
+ _onopen: function() {
this._isOpen = true;
// Reset the reconnect delay, since we connected successfully.
this._resetDelay();
var handler = this.getOpenHandler();
if (handler) {
handler();
}
},
/**
* Reset the reconnect delay to its base value.
*/
_resetDelay: function() {
this._delayUntilReconnect = 2000;
},
/**
* Callback for message received.
*/
_onmessage: function(e) {
var data = e.data;
var handler = this.getMessageHandler();
if (handler) {
handler(data);
}
},
/**
* Callback for connection close.
*/
- _onclose: function(e) {
+ _onclose: function() {
this._isOpen = false;
var done = false;
var handler = this.getCloseHandler();
if (handler) {
done = handler();
}
// If we didn't explicitly see a close() call and the close handler
// did not return `true` to stop the reconnect, wait a little while
// and try to reconnect.
if (!done && !this._shouldClose) {
setTimeout(JX.bind(this, this._reconnect), this._delayUntilReconnect);
}
},
/**
* Reconnect an interrupted socket.
*/
_reconnect: function() {
// Increase the reconnect delay by a factor of 2. If we fail to open the
// connection, the close handler will send us back here. We'll reconnect
// more and more slowly until we eventually get a valid connection.
this._delayUntilReconnect = this._delayUntilReconnect * 2;
this.open();
}
}
});
diff --git a/webroot/rsrc/externals/javelin/lib/__tests__/JSON.js b/webroot/rsrc/externals/javelin/lib/__tests__/JSON.js
index 539a41610e..05b236307d 100644
--- a/webroot/rsrc/externals/javelin/lib/__tests__/JSON.js
+++ b/webroot/rsrc/externals/javelin/lib/__tests__/JSON.js
@@ -1,36 +1,36 @@
/**
* @requires javelin-json
*/
describe('JSON', function() {
it('should encode and decode an object', function() {
var object = {
a: [0, 1, 2],
- s: "Javelin Stuffs",
+ s: 'Javelin Stuffs',
u: '\x01',
n: 1,
f: 3.14,
b: false,
nil: null,
o: {
a: 1,
b: [1, 2],
c: {
a: 2,
b: 3
}
}
};
expect(JX.JSON.parse(JX.JSON.stringify(object))).toEqual(object);
});
it('should encode undefined array indices as null', function() {
var a = [];
a.length = 2;
var o = { x : a };
expect(JX.JSON.stringify(o)).toEqual('{"x":[null,null]}');
});
});
diff --git a/webroot/rsrc/externals/javelin/lib/__tests__/URI.js b/webroot/rsrc/externals/javelin/lib/__tests__/URI.js
index a162ad59b8..382ba3ca7e 100644
--- a/webroot/rsrc/externals/javelin/lib/__tests__/URI.js
+++ b/webroot/rsrc/externals/javelin/lib/__tests__/URI.js
@@ -1,302 +1,302 @@
/**
* @requires javelin-uri javelin-php-serializer
*/
describe('Javelin URI', function() {
it('should understand parts of a uri', function() {
var uri = JX.$U('http://www.facebook.com:123/home.php?key=value#fragment');
expect(uri.getProtocol()).toEqual('http');
expect(uri.getDomain()).toEqual('www.facebook.com');
expect(uri.getPort()).toEqual('123');
expect(uri.getPath()).toEqual('/home.php');
expect(uri.getQueryParams()).toEqual({'key' : 'value'});
expect(uri.getFragment()).toEqual('fragment');
});
it('can accept null as uri string', function() {
var uri = JX.$U(null);
expect(uri.getProtocol()).toEqual(undefined);
expect(uri.getDomain()).toEqual(undefined);
expect(uri.getPath()).toEqual(undefined);
expect(uri.getQueryParams()).toEqual({});
expect(uri.getFragment()).toEqual(undefined);
expect(uri.toString()).toEqual('');
});
it('can accept empty string as uri string', function() {
var uri = JX.$U('');
expect(uri.getProtocol()).toEqual(undefined);
expect(uri.getDomain()).toEqual(undefined);
expect(uri.getPath()).toEqual(undefined);
expect(uri.getQueryParams()).toEqual({});
expect(uri.getFragment()).toEqual(undefined);
expect(uri.toString()).toEqual('');
});
it('should understand relative uri', function() {
var uri = JX.$U('/home.php?key=value#fragment');
expect(uri.getProtocol()).toEqual(undefined);
expect(uri.getDomain()).toEqual(undefined);
expect(uri.getPath()).toEqual('/home.php');
expect(uri.getQueryParams()).toEqual({'key' : 'value'});
expect(uri.getFragment()).toEqual('fragment');
});
function charRange(from, to) {
- res = '';
+ var res = '';
for (var i = from.charCodeAt(0); i <= to.charCodeAt(0); i++) {
res += String.fromCharCode(i);
}
return res;
}
it('should reject unsafe domains', function() {
var unsafe_chars =
'\x00;\\%\u2047\u2048\ufe56\ufe5f\uff03\uff0f\uff1f' +
charRange('\ufdd0', '\ufdef') + charRange('\ufff0', '\uffff');
for (var i = 0; i < unsafe_chars.length; i++) {
expect(function() {
JX.$U('http://foo' + unsafe_chars.charAt(i) + 'bar');
}).toThrow();
}
});
it('should allow safe domains', function() {
var safe_chars =
'-._' + charRange('a', 'z') + charRange('A', 'Z') + charRange('0', '9') +
'\u2046\u2049\ufdcf\ufdf0\uffef';
for (var i = 0; i < safe_chars.length; i++) {
var domain = 'foo' + safe_chars.charAt(i) + 'bar';
var uri = JX.$U('http://' + domain);
expect(uri.getDomain()).toEqual(domain);
}
});
it('should set slash as the default path', function() {
var uri = JX.$U('http://www.facebook.com');
expect(uri.getPath()).toEqual('/');
});
it('should set empty map as the default query data', function() {
var uri = JX.$U('http://www.facebook.com/');
expect(uri.getQueryParams()).toEqual({});
});
it('should set undefined as the default fragment', function() {
var uri = JX.$U('http://www.facebook.com/');
expect(uri.getFragment()).toEqual(undefined);
});
it('should understand uri with no path', function() {
var uri = JX.$U('http://www.facebook.com?key=value');
expect(uri.getPath()).toEqual('/');
expect(uri.getQueryParams()).toEqual({'key' : 'value'});
});
it('should understand multiple query keys', function() {
var uri = JX.$U('/?clown=town&herp=derp');
expect(uri.getQueryParams()).toEqual({
'clown' : 'town',
'herp' : 'derp'
});
});
it('does not set keys for nonexistant data', function() {
var uri = JX.$U('/?clown=town');
expect(uri.getQueryParams().herp).toEqual(undefined);
});
it('does not parse different types of query data', function() {
var uri = JX.$U('/?str=string&int=123&bool=true&badbool=false&raw');
expect(uri.getQueryParams()).toEqual({
'str' : 'string',
'int' : '123',
'bool' : 'true',
'badbool' : 'false',
'raw' : ''
});
});
it('should act as string', function() {
var string = 'http://www.facebook.com/home.php?key=value';
var uri = JX.$U(string);
expect(uri.toString()).toEqual(string);
expect('' + uri).toEqual(string);
});
it('can remove path', function() {
var uri = JX.$U('http://www.facebook.com/home.php?key=value');
uri.setPath(undefined);
expect(uri.getPath()).toEqual(undefined);
expect(uri.toString()).toEqual('http://www.facebook.com/?key=value');
});
it('can remove queryData by undefining it', function() {
var uri = JX.$U('http://www.facebook.com/home.php?key=value');
uri.setQueryParams(undefined);
expect(uri.getQueryParams()).toEqual(undefined);
expect(uri.toString()).toEqual('http://www.facebook.com/home.php');
});
it('can remove queryData by replacing it', function() {
var uri = JX.$U('http://www.facebook.com/home.php?key=value');
uri.setQueryParams({});
expect(uri.getQueryParams()).toEqual({});
expect(uri.toString()).toEqual('http://www.facebook.com/home.php');
});
it('can amend to removed queryData', function() {
var uri = JX.$U('http://www.facebook.com/home.php?key=value');
uri.setQueryParams({});
expect(uri.getQueryParams()).toEqual({});
uri.addQueryParams({'herp' : 'derp'});
expect(uri.getQueryParams()).toEqual({'herp' : 'derp'});
expect(uri.toString()).toEqual(
'http://www.facebook.com/home.php?herp=derp');
});
it('should properly decode entities', function() {
var uri = JX.$U('/?from=clown+town&to=cloud%20city&pass=cloud%2Bcountry');
expect(uri.getQueryParams()).toEqual({
'from' : 'clown town',
'to' : 'cloud city',
'pass' : 'cloud+country'
});
expect(uri.toString()).toEqual(
'/?from=clown%20town&to=cloud%20city&pass=cloud%2Bcountry');
});
it('can add query data', function() {
var uri = JX.$U('http://www.facebook.com/');
uri.addQueryParams({'key' : 'value'});
expect(uri.getQueryParams()).toEqual({'key' : 'value'});
expect(uri.toString()).toEqual('http://www.facebook.com/?key=value');
uri.setQueryParam('key', 'lock');
expect(uri.getQueryParams()).toEqual({'key' : 'lock'});
expect(uri.toString()).toEqual('http://www.facebook.com/?key=lock');
});
it('can add different types of query data', function() {
var uri = new JX.URI();
uri.setQueryParams({
'str' : 'string',
'int' : 123,
'bool' : true,
'badbool' : false,
'raw' : ''
});
expect(uri.toString()).toEqual(
'?str=string&int=123&bool=true&badbool=false&raw');
});
it('should properly encode entities in added query data', function() {
var uri = new JX.URI();
uri.addQueryParams({'key' : 'two words'});
expect(uri.getQueryParams()).toEqual({'key' : 'two words'});
expect(uri.toString()).toEqual('?key=two%20words');
});
it('can add multiple query data', function() {
var uri = JX.$U('http://www.facebook.com/');
uri.addQueryParams({
'clown' : 'town',
'herp' : 'derp'
});
expect(uri.getQueryParams()).toEqual({
'clown' : 'town',
'herp' : 'derp'
});
expect(uri.toString()).toEqual(
'http://www.facebook.com/?clown=town&herp=derp');
});
it('can append to existing query data', function() {
var uri = JX.$U('/?key=value');
uri.addQueryParams({'clown' : 'town'});
expect(uri.getQueryParams()).toEqual({
'key' : 'value',
'clown' : 'town'
});
expect(uri.toString()).toEqual('/?key=value&clown=town');
});
it('can merge with existing query data', function() {
var uri = JX.$U('/?key=value&clown=town');
uri.addQueryParams({
'clown' : 'ville',
'herp' : 'derp'
});
expect(uri.getQueryParams()).toEqual({
'key' : 'value',
'clown' : 'ville',
'herp' : 'derp'
});
expect(uri.toString()).toEqual('/?key=value&clown=ville&herp=derp');
});
it('can replace query data', function() {
var uri = JX.$U('/?key=value&clown=town');
uri.setQueryParams({'herp' : 'derp'});
expect(uri.getQueryParams()).toEqual({'herp' : 'derp'});
expect(uri.toString()).toEqual('/?herp=derp');
});
it('can remove query data', function() {
var uri = JX.$U('/?key=value&clown=town');
uri.addQueryParams({'key' : null});
expect(uri.getQueryParams()).toEqual({
'clown' : 'town',
'key' : null
});
expect(uri.toString()).toEqual('/?clown=town');
});
it('can remove multiple query data', function() {
var uri = JX.$U('/?key=value&clown=town&herp=derp');
uri.addQueryParams({'key' : null, 'herp' : undefined});
expect(uri.getQueryParams()).toEqual({
'clown' : 'town',
'key' : null,
'herp' : undefined
});
expect(uri.toString()).toEqual('/?clown=town');
});
it('can remove non existent query data', function() {
var uri = JX.$U('/?key=value');
uri.addQueryParams({'magic' : null});
expect(uri.getQueryParams()).toEqual({
'key' : 'value',
'magic' : null
});
expect(uri.toString()).toEqual('/?key=value');
});
it('can build uri from scratch', function() {
var uri = new JX.URI();
uri.setProtocol('http');
uri.setDomain('www.facebook.com');
uri.setPath('/home.php');
uri.setQueryParams({'key' : 'value'});
uri.setFragment('fragment');
expect(uri.toString()).toEqual(
'http://www.facebook.com/home.php?key=value#fragment');
});
it('no global state interference', function() {
var uri1 = JX.$U('/?key=value');
var uri2 = JX.$U();
expect(uri2.getQueryParams()).not.toEqual({'key' : 'value'});
});
it('should not loop indefinitely when parsing empty params', function() {
expect(JX.$U('/?&key=value').getQueryParams()).toEqual({'key' : 'value'});
expect(JX.$U('/?&&&key=value').getQueryParams()).toEqual({'key' : 'value'});
expect(JX.$U('/?&&').getQueryParams()).toEqual({});
});
it('should parse values with =', function() {
expect(JX.$U('/?x=1=1').getQueryParams()).toEqual({'x' : '1=1'});
});
});
diff --git a/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js b/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js
index dc516f3de5..fd5aae35d8 100644
--- a/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js
+++ b/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js
@@ -1,439 +1,435 @@
/**
* @requires javelin-dom
* javelin-util
* javelin-stratcom
* javelin-install
* @provides javelin-tokenizer
* @javelin
*/
/**
* A tokenizer is a UI component similar to a text input, except that it
* allows the user to input a list of items ("tokens"), generally from a fixed
* set of results. A familiar example of this UI is the "To:" field of most
* email clients, where the control autocompletes addresses from the user's
* address book.
*
* @{JX.Tokenizer} is built on top of @{JX.Typeahead}, and primarily adds the
* ability to choose multiple items.
*
* To build a @{JX.Tokenizer}, you need to do four things:
*
* 1. Construct it, padding a DOM node for it to attach to. See the constructor
* for more information.
* 2. Build a {@JX.Typeahead} and configure it with setTypeahead().
* 3. Configure any special options you want.
* 4. Call start().
*
* If you do this correctly, the input should suggest items and enter them as
* tokens as the user types.
*
* When the tokenizer is focused, the CSS class `jx-tokenizer-container-focused`
* is added to the container node.
*/
JX.install('Tokenizer', {
construct : function(containerNode) {
this._containerNode = containerNode;
},
events : [
/**
* Emitted when the value of the tokenizer changes, similar to an 'onchange'
* from a <select />.
*/
'change'],
properties : {
limit : null,
renderTokenCallback : null
},
members : {
_containerNode : null,
_root : null,
_focus : null,
_orig : null,
_typeahead : null,
_tokenid : 0,
_tokens : null,
_tokenMap : null,
_initialValue : null,
_seq : 0,
_lastvalue : null,
_placeholder : null,
start : function() {
if (__DEV__) {
if (!this._typeahead) {
throw new Error(
'JX.Tokenizer.start(): ' +
'No typeahead configured! Use setTypeahead() to provide a ' +
'typeahead.');
}
}
this._orig = JX.DOM.find(this._containerNode, 'input', 'tokenizer-input');
this._tokens = [];
this._tokenMap = {};
var focus = this.buildInput(this._orig.value);
this._focus = focus;
var input_container = JX.DOM.scry(
this._containerNode,
'div',
'tokenizer-input-container'
);
input_container = input_container[0] || this._containerNode;
JX.DOM.listen(
focus,
['click', 'focus', 'blur', 'keydown', 'keypress', 'paste'],
null,
JX.bind(this, this.handleEvent));
// NOTE: Safari on the iPhone does not normally delegate click events on
// <div /> tags. This causes the event to fire. We want a click (in this
// case, a touch) anywhere in the div to trigger this event so that we
// can focus the input. Without this, you must tap an arbitrary area on
// the left side of the input to focus it.
//
// http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
input_container.onclick = JX.bag;
JX.DOM.listen(
input_container,
'click',
null,
JX.bind(
this,
function(e) {
if (e.getNode('remove')) {
this._remove(e.getNodeData('token').key, true);
} else if (e.getTarget() == this._root) {
this.focus();
}
}));
var root = JX.$N('div');
root.id = this._orig.id;
JX.DOM.alterClass(root, 'jx-tokenizer', true);
root.style.cursor = 'text';
this._root = root;
root.appendChild(focus);
var typeahead = this._typeahead;
typeahead.setInputNode(this._focus);
typeahead.start();
setTimeout(JX.bind(this, function() {
var container = this._orig.parentNode;
JX.DOM.setContent(container, root);
var map = this._initialValue || {};
for (var k in map) {
this.addToken(k, map[k]);
}
JX.DOM.appendContent(
root,
JX.$N('div', {style: {clear: 'both'}})
);
this._redraw();
}), 0);
},
setInitialValue : function(map) {
this._initialValue = map;
return this;
},
setTypeahead : function(typeahead) {
typeahead.setAllowNullSelection(false);
typeahead.removeListener();
typeahead.listen(
'choose',
JX.bind(this, function(result) {
JX.Stratcom.context().prevent();
if (this.addToken(result.rel, result.name)) {
if (this.shouldHideResultsOnChoose()) {
this._typeahead.hide();
}
this._typeahead.clear();
this._redraw();
this.focus();
}
})
);
typeahead.listen(
'query',
JX.bind(
this,
function(query) {
// TODO: We should emit a 'query' event here to allow the caller to
// generate tokens on the fly, e.g. email addresses or other freeform
// or algorithmic tokens.
// Then do this if something handles the event.
// this._focus.value = '';
// this._redraw();
// this.focus();
if (query.length) {
// Prevent this event if there's any text, so that we don't submit
// the form (either we created a token or we failed to create a
// token; in either case we shouldn't submit). If the query is
// empty, allow the event so that the form submission takes place.
JX.Stratcom.context().prevent();
}
}));
this._typeahead = typeahead;
return this;
},
shouldHideResultsOnChoose : function() {
return true;
},
handleEvent : function(e) {
this._typeahead.handleEvent(e);
if (e.getPrevented()) {
return;
}
if (e.getType() == 'click') {
if (e.getTarget() == this._root) {
this.focus();
e.prevent();
return;
}
} else if (e.getType() == 'keydown') {
this._onkeydown(e);
} else if (e.getType() == 'blur') {
this._didblur();
// Explicitly update the placeholder since we just wiped the field
// value.
this._typeahead.updatePlaceholder();
} else if (e.getType() == 'focus') {
this._didfocus();
} else if (e.getType() == 'paste') {
setTimeout(JX.bind(this, this._redraw), 0);
}
},
refresh : function() {
this._redraw(true);
return this;
},
_redraw : function(force) {
// If there are tokens in the tokenizer, never show a placeholder.
// Otherwise, show one if one is configured.
if (JX.keys(this._tokenMap).length) {
this._typeahead.setPlaceholder(null);
} else {
this._typeahead.setPlaceholder(this._placeholder);
}
var focus = this._focus;
if (focus.value === this._lastvalue && !force) {
return;
}
this._lastvalue = focus.value;
- var root = this._root;
var metrics = JX.DOM.textMetrics(
this._focus,
'jx-tokenizer-metrics');
metrics.y = null;
metrics.x += 24;
metrics.setDim(focus);
// NOTE: Once, long ago, we set "focus.value = focus.value;" here to fix
// an issue with copy/paste in Firefox not redrawing correctly. However,
// this breaks input of Japanese glyphs in Chrome, and I can't reproduce
// the original issue in modern Firefox.
//
// If future changes muck around with things here, test that Japanese
// inputs still work. Example:
//
// - Switch to Hiragana mode.
// - Type "ni".
// - This should produce a glyph, not the value "n".
//
// With the assignment, Chrome loses the partial input on the "n" when
// the value is assigned.
},
setPlaceholder : function(string) {
this._placeholder = string;
return this;
},
addToken : function(key, value) {
if (key in this._tokenMap) {
return false;
}
var focus = this._focus;
var root = this._root;
var token = this.buildToken(key, value);
this._tokenMap[key] = {
value : value,
key : key,
node : token
};
this._tokens.push(key);
root.insertBefore(token, focus);
this.invoke('change', this);
return true;
},
removeToken : function(key) {
return this._remove(key, false);
},
buildInput: function(value) {
return JX.$N('input', {
className: 'jx-tokenizer-input',
type: 'text',
autocomplete: 'off',
value: value
});
},
/**
* Generate a token based on a key and value. The "token" and "remove"
* sigils are observed by a listener in start().
*/
buildToken: function(key, value) {
var input = JX.$N('input', {
type: 'hidden',
value: key,
name: this._orig.name + '[' + (this._seq++) + ']'
});
var remove = JX.$N('a', {
className: 'jx-tokenizer-x',
sigil: 'remove'
}, '\u00d7'); // U+00D7 multiplication sign
var display_token = value;
var render_callback = this.getRenderTokenCallback();
if (render_callback) {
display_token = render_callback(value, key);
}
return JX.$N('a', {
className: 'jx-tokenizer-token',
sigil: 'token',
meta: {key: key}
}, [display_token, input, remove]);
},
getTokens : function() {
var result = {};
for (var key in this._tokenMap) {
result[key] = this._tokenMap[key].value;
}
return result;
},
_onkeydown : function(e) {
- var focus = this._focus;
- var root = this._root;
-
var raw = e.getRawEvent();
if (raw.ctrlKey || raw.metaKey || raw.altKey) {
return;
}
switch (e.getSpecialKey()) {
case 'tab':
var completed = this._typeahead.submit();
if (!completed) {
this._focus.value = '';
}
break;
case 'delete':
if (!this._focus.value.length) {
var tok;
while ((tok = this._tokens.pop())) {
if (this._remove(tok, true)) {
break;
}
}
}
break;
case 'return':
// Don't subject this to token limits.
break;
default:
if (this.getLimit() &&
JX.keys(this._tokenMap).length == this.getLimit()) {
e.prevent();
}
setTimeout(JX.bind(this, this._redraw), 0);
break;
}
},
_remove : function(index, focus) {
if (!this._tokenMap[index]) {
return false;
}
JX.DOM.remove(this._tokenMap[index].node);
delete this._tokenMap[index];
this._redraw(true);
focus && this.focus();
this.invoke('change', this);
return true;
},
focus : function() {
var focus = this._focus;
JX.DOM.show(focus);
// NOTE: We must fire this focus event immediately (during event
// handling) for the iPhone to bring up the keyboard. Previously this
// focus was wrapped in setTimeout(), but it's unclear why that was
// necessary. If this is adjusted later, make sure tapping the inactive
// area of the tokenizer to focus it on the iPhone still brings up the
// keyboard.
JX.DOM.focus(focus);
},
_didfocus : function() {
JX.DOM.alterClass(
this._containerNode,
'jx-tokenizer-container-focused',
true);
},
_didblur : function() {
JX.DOM.alterClass(
this._containerNode,
'jx-tokenizer-container-focused',
false);
this._focus.value = '';
this._redraw();
}
}
});
diff --git a/webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js b/webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js
index d8f8aa27bf..5875bf9ea6 100644
--- a/webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js
+++ b/webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js
@@ -1,560 +1,560 @@
/**
* @requires javelin-install
* javelin-dom
* javelin-vector
* javelin-util
* @provides javelin-typeahead
* @javelin
*/
/**
* A typeahead is a UI component similar to a text input, except that it
* suggests some set of results (like friends' names, common searches, or
* repository paths) as the user types them. Familiar examples of this UI
* include Google Suggest, the Facebook search box, and OS X's Spotlight
* feature.
*
* To build a @{JX.Typeahead}, you need to do four things:
*
* 1. Construct it, passing some DOM nodes for it to attach to. See the
* constructor for more information.
* 2. Attach a datasource by calling setDatasource() with a valid datasource,
* often a @{JX.TypeaheadPreloadedSource}.
* 3. Configure any special options that you want.
* 4. Call start().
*
* If you do this correctly, a dropdown menu should appear under the input as
* the user types, suggesting matching results.
*
* @task build Building a Typeahead
* @task datasource Configuring a Datasource
* @task config Configuring Options
* @task start Activating a Typeahead
* @task control Controlling Typeaheads from Javascript
* @task internal Internal Methods
*/
JX.install('Typeahead', {
/**
* Construct a new Typeahead on some "hardpoint". At a minimum, the hardpoint
* should be a ##<div>## with "position: relative;" wrapped around a text
* ##<input>##. The typeahead's dropdown suggestions will be appended to the
* hardpoint in the DOM. Basically, this is the bare minimum requirement:
*
* LANG=HTML
* <div style="position: relative;">
* <input type="text" />
* </div>
*
* Then get a reference to the ##<div>## and pass it as 'hardpoint', and pass
* the ##<input>## as 'control'. This will enhance your boring old
* ##<input />## with amazing typeahead powers.
*
* On the Facebook/Tools stack, ##<javelin:typeahead-template />## can build
* this for you.
*
* @param Node "Hardpoint", basically an anchorpoint in the document which
* the typeahead can append its suggestion menu to.
* @param Node? Actual ##<input />## to use; if not provided, the typeahead
* will just look for a (solitary) input inside the hardpoint.
* @task build
*/
construct : function(hardpoint, control) {
this._hardpoint = hardpoint;
this._control = control || JX.DOM.find(hardpoint, 'input');
this._root = JX.$N(
'div',
{className: 'jx-typeahead-results'});
this._display = [];
this._listener = JX.DOM.listen(
this._control,
['focus', 'blur', 'keypress', 'keydown', 'input'],
null,
JX.bind(this, this.handleEvent));
JX.DOM.listen(
this._root,
['mouseover', 'mouseout'],
null,
JX.bind(this, this._onmouse));
JX.DOM.listen(
this._root,
'mousedown',
'tag:a',
JX.bind(this, function(e) {
if (!e.isRightButton()) {
this._choose(e.getNode('tag:a'));
}
}));
},
events : ['choose', 'query', 'start', 'change', 'show'],
properties : {
/**
* Boolean. If true (default), the user is permitted to submit the typeahead
* with a custom or empty selection. This is a good behavior if the
* typeahead is attached to something like a search input, where the user
* might type a freeform query or select from a list of suggestions.
* However, sometimes you require a specific input (e.g., choosing which
* user owns something), in which case you can prevent null selections.
*
* @task config
*/
allowNullSelection : true
},
members : {
_root : null,
_control : null,
_hardpoint : null,
_listener : null,
_value : null,
_stop : false,
_focus : -1,
_focused : false,
_placeholderVisible : false,
_placeholder : null,
_display : null,
_datasource : null,
_waitingListener : null,
_readyListener : null,
_completeListener : null,
/**
* Activate your properly configured typeahead. It won't do anything until
* you call this method!
*
* @task start
* @return void
*/
start : function() {
this.invoke('start');
if (__DEV__) {
if (!this._datasource) {
throw new Error(
- "JX.Typeahead.start(): " +
- "No datasource configured. Create a datasource and call " +
- "setDatasource().");
+ 'JX.Typeahead.start(): ' +
+ 'No datasource configured. Create a datasource and call ' +
+ 'setDatasource().');
}
}
this.updatePlaceholder();
},
/**
* Configure a datasource, which is where the Typeahead gets suggestions
* from. See @{JX.TypeaheadDatasource} for more information. You must
* provide exactly one datasource.
*
* @task datasource
* @param JX.TypeaheadDatasource The datasource which the typeahead will
* draw from.
*/
setDatasource : function(datasource) {
if (this._datasource) {
this._datasource.unbindFromTypeahead();
this._waitingListener.remove();
this._readyListener.remove();
this._completeListener.remove();
}
this._waitingListener = datasource.listen(
'waiting',
JX.bind(this, this.waitForResults));
this._readyListener = datasource.listen(
'resultsready',
JX.bind(this, this.showResults));
this._completeListener = datasource.listen(
'complete',
JX.bind(this, this.doneWaitingForResults));
datasource.bindToTypeahead(this);
this._datasource = datasource;
},
getDatasource : function() {
return this._datasource;
},
/**
* Override the <input /> selected in the constructor with some other input.
* This is primarily useful when building a control on top of the typeahead,
* like @{JX.Tokenizer}.
*
* @task config
* @param node An <input /> node to use as the primary control.
*/
setInputNode : function(input) {
this._control = input;
return this;
},
/**
* Hide the typeahead's dropdown suggestion menu.
*
* @task control
* @return void
*/
hide : function() {
this._changeFocus(Number.NEGATIVE_INFINITY);
this._display = [];
this._moused = false;
JX.DOM.hide(this._root);
},
/**
* Show a given result set in the typeahead's dropdown suggestion menu.
* Normally, you don't call this method directly. Usually it gets called
* in response to events from the datasource you have configured.
*
* @task control
* @param list List of ##<a />## tags to show as suggestions/results.
* @param string The query this result list corresponds to.
* @return void
*/
showResults : function(results, value) {
if (value != this._value) {
// This result list is for an old query, and no longer represents
// the input state of the typeahead.
// For example, the user may have typed "dog", and then they delete
// their query and type "cat", and then the "dog" results arrive from
// the source.
// Another case is that the user made a selection in a tokenizer,
// and then results returned. However, the typeahead is now empty, and
// we don't want to pop it back open.
// In all cases, just throw these results away. They are no longer
// relevant.
return;
}
var obj = {show: results};
var e = this.invoke('show', obj);
// If the user has an element focused, store the value before we redraw.
// After we redraw, try to select the same element if it still exists in
// the list. This prevents redraws from disrupting keyboard element
// selection.
var old_focus = null;
if (this._focus >= 0 && this._display[this._focus]) {
old_focus = this._display[this._focus].name;
}
// Note that the results list may have been update by the "show" event
// listener. Non-result node (e.g. divider or label) may have been
// inserted.
JX.DOM.setContent(this._root, results);
this._display = JX.DOM.scry(this._root, 'a', 'typeahead-result');
if (this._display.length && !e.getPrevented()) {
this._changeFocus(Number.NEGATIVE_INFINITY);
var d = JX.Vector.getDim(this._hardpoint);
d.x = 0;
d.setPos(this._root);
if (this._root.parentNode !== this._hardpoint) {
this._hardpoint.appendChild(this._root);
}
JX.DOM.show(this._root);
// If we had a node focused before, look for a node with the same value
// and focus it.
if (old_focus !== null) {
for (var ii = 0; ii < this._display.length; ii++) {
if (this._display[ii].name == old_focus) {
this._focus = ii;
this._drawFocus();
break;
}
}
}
} else {
this.hide();
JX.DOM.setContent(this._root, null);
}
},
refresh : function() {
if (this._stop) {
return;
}
this._value = this._control.value;
this.invoke('change', this._value);
},
/**
* Show a "waiting for results" UI. We may be showing a partial result set
* at this time, if the user is extending a query we already have results
* for.
*
* @task control
* @return void
*/
waitForResults : function() {
JX.DOM.alterClass(this._hardpoint, 'jx-typeahead-waiting', true);
},
/**
* Hide the "waiting for results" UI.
*
* @task control
* @return void
*/
doneWaitingForResults : function() {
JX.DOM.alterClass(this._hardpoint, 'jx-typeahead-waiting', false);
},
/**
* @task internal
*/
_onmouse : function(event) {
this._moused = (event.getType() == 'mouseover');
this._drawFocus();
},
/**
* @task internal
*/
_changeFocus : function(d) {
var n = Math.min(Math.max(-1, this._focus + d), this._display.length - 1);
if (!this.getAllowNullSelection()) {
n = Math.max(0, n);
}
if (this._focus >= 0 && this._focus < this._display.length) {
JX.DOM.alterClass(this._display[this._focus], 'focused', false);
}
this._focus = n;
this._drawFocus();
return true;
},
/**
* @task internal
*/
_drawFocus : function() {
var f = this._display[this._focus];
if (f) {
JX.DOM.alterClass(f, 'focused', !this._moused);
}
},
/**
* @task internal
*/
_choose : function(target) {
var result = this.invoke('choose', target);
if (result.getPrevented()) {
return;
}
this._control.value = target.name;
this.hide();
},
/**
* @task control
*/
clear : function() {
this._control.value = '';
this._value = '';
this.hide();
},
/**
* @task control
*/
enable : function() {
this._control.disabled = false;
this._stop = false;
},
/**
* @task control
*/
disable : function() {
this._control.blur();
this._control.disabled = true;
this._stop = true;
},
/**
* @task control
*/
submit : function() {
if (this._focus >= 0 && this._display[this._focus]) {
this._choose(this._display[this._focus]);
return true;
} else {
- result = this.invoke('query', this._control.value);
+ var result = this.invoke('query', this._control.value);
if (result.getPrevented()) {
return true;
}
}
return false;
},
setValue : function(value) {
this._control.value = value;
},
getValue : function() {
return this._control.value;
},
/**
* @task internal
*/
_update : function(event) {
if (event.getType() == 'focus') {
this._focused = true;
this.updatePlaceholder();
}
var k = event.getSpecialKey();
if (k && event.getType() == 'keydown') {
switch (k) {
case 'up':
if (this._display.length && this._changeFocus(-1)) {
event.prevent();
}
break;
case 'down':
if (this._display.length && this._changeFocus(1)) {
event.prevent();
}
break;
case 'return':
if (this.submit()) {
event.prevent();
return;
}
break;
case 'esc':
if (this._display.length && this.getAllowNullSelection()) {
this.hide();
event.prevent();
}
break;
case 'tab':
// If the user tabs out of the field, don't refresh.
return;
}
}
// We need to defer because the keystroke won't be present in the input's
// value field yet.
setTimeout(JX.bind(this, function() {
if (this._value == this._control.value) {
// The typeahead value hasn't changed.
return;
}
this.refresh();
}), 0);
},
/**
* This method is pretty much internal but @{JX.Tokenizer} needs access to
* it for delegation. You might also need to delegate events here if you
* build some kind of meta-control.
*
* Reacts to user events in accordance to configuration.
*
* @task internal
* @param JX.Event User event, like a click or keypress.
* @return void
*/
handleEvent : function(e) {
if (this._stop || e.getPrevented()) {
return;
}
var type = e.getType();
if (type == 'blur') {
this._focused = false;
this.updatePlaceholder();
this.hide();
} else {
this._update(e);
}
},
removeListener : function() {
if (this._listener) {
this._listener.remove();
}
},
/**
* Set a string to display in the control when it is not focused, like
* "Type a user's name...". This string hints to the user how to use the
* control.
*
* When the string is displayed, the input will have class
* "jx-typeahead-placeholder".
*
* @param string Placeholder string, or null for no placeholder.
* @return this
*
* @task config
*/
setPlaceholder : function(string) {
this._placeholder = string;
this.updatePlaceholder();
return this;
},
/**
* Update the control to either show or hide the placeholder text as
* necessary.
*
* @return void
* @task internal
*/
updatePlaceholder : function() {
if (this._placeholderVisible) {
// If the placeholder is visible, we want to hide if the control has
// been focused or the placeholder has been removed.
if (this._focused || !this._placeholder) {
this._placeholderVisible = false;
this._control.value = '';
}
} else if (!this._focused) {
// If the placeholder is not visible, we want to show it if the control
// has benen blurred.
if (this._placeholder && !this._control.value) {
this._placeholderVisible = true;
}
}
if (this._placeholderVisible) {
// We need to resist the Tokenizer wiping the input on blur.
this._control.value = this._placeholder;
}
JX.DOM.alterClass(
this._control,
'jx-typeahead-placeholder',
this._placeholderVisible);
}
}
});
diff --git a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js
index 3ce355a2e0..31b9866d46 100644
--- a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js
+++ b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js
@@ -1,371 +1,371 @@
/**
* @requires javelin-install
* javelin-util
* javelin-dom
* javelin-typeahead-normalizer
* @provides javelin-typeahead-source
* @javelin
*/
JX.install('TypeaheadSource', {
construct : function() {
this._raw = {};
this._lookup = {};
this.setNormalizer(JX.TypeaheadNormalizer.normalize);
this._excludeIDs = {};
},
events : ['waiting', 'resultsready', 'complete'],
properties : {
/**
* Allows you to specify a function which will be used to normalize strings.
* Strings are normalized before being tokenized, and before being sent to
* the server. The purpose of normalization is to strip out irrelevant data,
* like uppercase/lowercase, extra spaces, or punctuation. By default,
* the @{JX.TypeaheadNormalizer} is used to normalize strings, but you may
* want to provide a different normalizer, particularly if there are
* special characters with semantic meaning in your object names.
*
* @param function
*/
normalizer : null,
/**
* If a typeahead query should be processed before being normalized and
* tokenized, specify a queryExtractor.
*
* @param function
*/
queryExtractor : null,
/**
* Transformers convert data from a wire format to a runtime format. The
* transformation mechanism allows you to choose an efficient wire format
* and then expand it on the client side, rather than duplicating data
* over the wire. The transformation is applied to objects passed to
* addResult(). It should accept whatever sort of object you ship over the
* wire, and produce a dictionary with these keys:
*
* - **id**: a unique id for each object.
* - **name**: the string used for matching against user input.
* - **uri**: the URI corresponding with the object (must be present
* but need not be meaningful)
*
* You can also give:
* - **display**: the text or nodes to show in the DOM. Usually just the
* same as ##name##.
* - **tokenizable**: if you want to tokenize something other than the
* ##name##, for the typeahead to complete on, specify it here. A
* selected entry from the typeahead will still insert the ##name##
* into the input, but the ##tokenizable## field lets you complete on
* non-name things.
*
* The default transformer expects a three element list with elements
* [name, uri, id]. It assigns the first element to both ##name## and
* ##display##.
*
* @param function
*/
transformer : null,
/**
* Configures the maximum number of suggestions shown in the typeahead
* dropdown.
*
* @param int
*/
maximumResultCount : 5,
/**
* Optional function which is used to sort results. Inputs are the input
* string, the list of matches, and a default comparator. The function
* should sort the list for display. This is the minimum useful
* implementation:
*
* function(value, list, comparator) {
* list.sort(comparator);
* }
*
* Alternatively, you may pursue more creative implementations.
*
* The `value` is a raw string; you can bind the datasource into the
* function and use normalize() or tokenize() to parse it.
*
* The `list` is a list of objects returned from the transformer function,
* see the `transformer` property. These are the objects in the list which
* match the value.
*
* The `comparator` is a sort callback which implements sensible default
* sorting rules (e.g., alphabetic order), which you can use as a fallback
* if you just want to tweak the results (e.g., put some items at the top).
*
* The function is called after the user types some text, immediately before
* the possible completion results are displayed to the user.
*
* @param function
*/
sortHandler : null,
/**
* Optional function which is used to filter results before display. Inputs
* are the input string and a list of matches. The function should
* return a list of matches to display. This is the minimum useful
* implementation:
*
* function(value, list) {
* return list;
* }
*
* @param function
*/
filterHandler : null
},
members : {
_raw : null,
_lookup : null,
_excludeIDs : null,
_changeListener : null,
_startListener : null,
bindToTypeahead : function(typeahead) {
this._changeListener = typeahead.listen(
'change',
JX.bind(this, this.didChange)
);
this._startListener = typeahead.listen(
'start',
JX.bind(this, this.didStart)
);
},
unbindFromTypeahead : function() {
this._changeListener.remove();
this._startListener.remove();
},
- didChange : function(value) {
+ didChange : function() {
return;
},
didStart : function() {
return;
},
clearCache : function() {
this._raw = {};
this._lookup = {};
},
addExcludeID : function(id) {
if (id) {
this._excludeIDs[id] = true;
}
},
removeExcludeID : function (id) {
if (id) {
delete this._excludeIDs[id];
}
},
addResult : function(obj) {
obj = (this.getTransformer() || this._defaultTransformer)(obj);
if (obj.id in this._raw) {
// We're already aware of this result. This will happen if someone
// searches for "zeb" and then for "zebra" with a
// TypeaheadRequestSource, for example, or the datasource just doesn't
// dedupe things properly. Whatever the case, just ignore it.
return;
}
if (__DEV__) {
for (var k in {name : 1, id : 1, display : 1, uri : 1}) {
if (!(k in obj)) {
throw new Error(
"JX.TypeaheadSource.addResult(): " +
"result must have properties 'name', 'id', 'uri' and 'display'.");
}
}
}
this._raw[obj.id] = obj;
var t = this.tokenize(obj.tokenizable || obj.name);
for (var jj = 0; jj < t.length; ++jj) {
if (!this._lookup.hasOwnProperty(t[jj])) {
this._lookup[t[jj]] = [];
}
this._lookup[t[jj]].push(obj.id);
}
},
waitForResults : function() {
this.invoke('waiting');
return this;
},
/**
* Get the raw state of a result by its ID. A number of other events and
* mechanisms give a list of result IDs and limited additional data; if you
* need to act on the full result data you can look it up here.
*
* @param scalar Result ID.
* @return dict Corresponding raw result.
*/
getResult : function(id) {
return this._raw[id];
},
matchResults : function(value, partial) {
// This table keeps track of the number of tokens each potential match
// has actually matched. When we're done, the real matches are those
// which have matched every token (so the value is equal to the token
// list length).
var match_count = {};
// This keeps track of distinct matches. If the user searches for
// something like "Chris C" against "Chris Cox", the "C" will match
// both fragments. We need to make sure we only count distinct matches.
var match_fragments = {};
var matched = {};
var seen = {};
var query_extractor = this.getQueryExtractor();
if (query_extractor) {
value = query_extractor(value);
}
var t = this.tokenize(value);
// Sort tokens by longest-first. We match each name fragment with at
// most one token.
t.sort(function(u, v) { return v.length - u.length; });
for (var ii = 0; ii < t.length; ++ii) {
// Do something reasonable if the user types the same token twice; this
// is sort of stupid so maybe kill it?
if (t[ii] in seen) {
t.splice(ii--, 1);
continue;
}
seen[t[ii]] = true;
var fragment = t[ii];
for (var name_fragment in this._lookup) {
if (name_fragment.substr(0, fragment.length) === fragment) {
if (!(name_fragment in matched)) {
matched[name_fragment] = true;
} else {
continue;
}
var l = this._lookup[name_fragment];
for (var jj = 0; jj < l.length; ++jj) {
var match_id = l[jj];
if (!match_fragments[match_id]) {
match_fragments[match_id] = {};
}
if (!(fragment in match_fragments[match_id])) {
match_fragments[match_id][fragment] = true;
match_count[match_id] = (match_count[match_id] || 0) + 1;
}
}
}
}
}
var hits = [];
for (var k in match_count) {
if (match_count[k] == t.length && !this._excludeIDs[k]) {
hits.push(k);
}
}
this.filterAndSortHits(value, hits);
var nodes = this.renderNodes(value, hits);
this.invoke('resultsready', nodes, value);
if (!partial) {
this.invoke('complete');
}
},
filterAndSortHits : function(value, hits) {
var objs = [];
var ii;
for (ii = 0; ii < hits.length; ii++) {
objs.push(this._raw[hits[ii]]);
}
var default_comparator = function(u, v) {
var key_u = u.sort || u.name;
var key_v = v.sort || v.name;
return key_u.localeCompare(key_v);
};
var filter_handler = this.getFilterHandler() || function(value, list) {
return list;
};
objs = filter_handler(value, objs);
var sort_handler = this.getSortHandler() || function(value, list, cmp) {
list.sort(cmp);
};
sort_handler(value, objs, default_comparator);
hits.splice(0, hits.length);
for (ii = 0; ii < objs.length; ii++) {
hits.push(objs[ii].id);
}
},
renderNodes : function(value, hits) {
var n = Math.min(this.getMaximumResultCount(), hits.length);
var nodes = [];
for (var kk = 0; kk < n; kk++) {
nodes.push(this.createNode(this._raw[hits[kk]]));
}
return nodes;
},
createNode : function(data) {
return JX.$N(
'a',
{
sigil: 'typeahead-result',
href: data.uri,
name: data.name,
rel: data.id,
className: 'jx-result'
},
data.display
);
},
normalize : function(str) {
return this.getNormalizer()(str);
},
tokenize : function(str) {
str = this.normalize(str);
if (!str.length) {
return [];
}
return str.split(/\s/g);
},
_defaultTransformer : function(object) {
return {
name : object[0],
display : object[0],
uri : object[1],
id : object[2]
};
}
}
});
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Jan 19 2025, 19:25 (6 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127942
Default Alt Text
(147 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment