Page MenuHomePhorge

No OneTemporary

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

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)

Event Timeline