mercury
Version:
A truly modular frontend framework
1,847 lines (1,482 loc) • 110 kB
JavaScript
// mercury @ 14.0.0
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.mercury=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
'use strict';
var SingleEvent = _dereq_('geval/single');
var MultipleEvent = _dereq_('geval/multiple');
var extend = _dereq_('xtend');
/*
Pro tip: Don't require `mercury` itself.
require and depend on all these modules directly!
*/
var mercury = module.exports = {
// Entry
main: _dereq_('main-loop'),
app: app,
// Base
BaseEvent: _dereq_('value-event/base-event'),
// Input
Delegator: _dereq_('dom-delegator'),
// deprecated: use hg.channels instead.
input: input,
// deprecated: use hg.channels instead.
handles: channels,
channels: channels,
// deprecated: use hg.send instead.
event: _dereq_('value-event/event'),
send: _dereq_('value-event/event'),
// deprecated: use hg.sendValue instead.
valueEvent: _dereq_('value-event/value'),
sendValue: _dereq_('value-event/value'),
// deprecated: use hg.sendSubmit instead.
submitEvent: _dereq_('value-event/submit'),
sendSubmit: _dereq_('value-event/submit'),
// deprecated: use hg.sendChange instead.
changeEvent: _dereq_('value-event/change'),
sendChange: _dereq_('value-event/change'),
// deprecated: use hg.sendKey instead.
keyEvent: _dereq_('value-event/key'),
sendKey: _dereq_('value-event/key'),
// deprecated use hg.sendClick instead.
clickEvent: _dereq_('value-event/click'),
sendClick: _dereq_('value-event/click'),
// State
// remove from core: favor hg.varhash instead.
array: _dereq_('observ-array'),
struct: _dereq_('observ-struct'),
// deprecated: use hg.struct instead.
hash: _dereq_('observ-struct'),
varhash: _dereq_('observ-varhash'),
value: _dereq_('observ'),
state: state,
// Render
diff: _dereq_('virtual-dom/vtree/diff'),
patch: _dereq_('virtual-dom/vdom/patch'),
partial: _dereq_('vdom-thunk'),
create: _dereq_('virtual-dom/vdom/create-element'),
h: _dereq_('virtual-dom/virtual-hyperscript'),
// Utilities
// remove from core: require computed directly instead.
computed: _dereq_('observ/computed'),
// remove from core: require watch directly instead.
watch: _dereq_('observ/watch')
};
function input(names) {
if (!names) {
return SingleEvent();
}
return MultipleEvent(names);
}
function state(obj) {
var copy = extend(obj);
var $channels = copy.channels;
var $handles = copy.handles;
if ($channels) {
copy.channels = mercury.value(null);
} else if ($handles) {
copy.handles = mercury.value(null);
}
var observ = mercury.struct(copy);
if ($channels) {
observ.channels.set(mercury.channels($channels, observ));
} else if ($handles) {
observ.handles.set(mercury.channels($handles, observ));
}
return observ;
}
function channels(funcs, context) {
return Object.keys(funcs).reduce(createHandle, {});
function createHandle(acc, name) {
var handle = mercury.Delegator.allocateHandle(
funcs[name].bind(null, context));
acc[name] = handle;
return acc;
}
}
function app(elem, observ, render, opts) {
mercury.Delegator(opts);
var loop = mercury.main(observ(), render, extend({
diff: mercury.diff,
create: mercury.create,
patch: mercury.patch
}, opts));
if (elem) {
elem.appendChild(loop.target);
}
return observ(loop.update);
}
},{"dom-delegator":6,"geval/multiple":15,"geval/single":16,"main-loop":18,"observ":36,"observ-array":24,"observ-struct":31,"observ-varhash":33,"observ/computed":35,"observ/watch":37,"value-event/base-event":41,"value-event/change":42,"value-event/click":43,"value-event/event":44,"value-event/key":45,"value-event/submit":51,"value-event/value":52,"vdom-thunk":54,"virtual-dom/vdom/create-element":64,"virtual-dom/vdom/patch":67,"virtual-dom/virtual-hyperscript":71,"virtual-dom/vtree/diff":84,"xtend":87}],2:[function(_dereq_,module,exports){
},{}],3:[function(_dereq_,module,exports){
/**
* cuid.js
* Collision-resistant UID generator for browsers and node.
* Sequential for fast db lookups and recency sorting.
* Safe for element IDs and server-side lookups.
*
* Extracted from CLCTR
*
* Copyright (c) Eric Elliott 2012
* MIT License
*/
/*global window, navigator, document, require, process, module */
(function (app) {
'use strict';
var namespace = 'cuid',
c = 0,
blockSize = 4,
base = 36,
discreteValues = Math.pow(base, blockSize),
pad = function pad(num, size) {
var s = "000000000" + num;
return s.substr(s.length-size);
},
randomBlock = function randomBlock() {
return pad((Math.random() *
discreteValues << 0)
.toString(base), blockSize);
},
safeCounter = function () {
c = (c < discreteValues) ? c : 0;
c++; // this is not subliminal
return c - 1;
},
api = function cuid() {
// Starting with a lowercase letter makes
// it HTML element ID friendly.
var letter = 'c', // hard-coded allows for sequential access
// timestamp
// warning: this exposes the exact date and time
// that the uid was created.
timestamp = (new Date().getTime()).toString(base),
// Prevent same-machine collisions.
counter,
// A few chars to generate distinct ids for different
// clients (so different computers are far less
// likely to generate the same id)
fingerprint = api.fingerprint(),
// Grab some more chars from Math.random()
random = randomBlock() + randomBlock();
counter = pad(safeCounter().toString(base), blockSize);
return (letter + timestamp + counter + fingerprint + random);
};
api.slug = function slug() {
var date = new Date().getTime().toString(36),
counter,
print = api.fingerprint().slice(0,1) +
api.fingerprint().slice(-1),
random = randomBlock().slice(-2);
counter = safeCounter().toString(36).slice(-4);
return date.slice(-2) +
counter + print + random;
};
api.globalCount = function globalCount() {
// We want to cache the results of this
var cache = (function calc() {
var i,
count = 0;
for (i in window) {
count++;
}
return count;
}());
api.globalCount = function () { return cache; };
return cache;
};
api.fingerprint = function browserPrint() {
return pad((navigator.mimeTypes.length +
navigator.userAgent.length).toString(36) +
api.globalCount().toString(36), 4);
};
// don't change anything from here down.
if (app.register) {
app.register(namespace, api);
} else if (typeof module !== 'undefined') {
module.exports = api;
} else {
app[namespace] = api;
}
}(this.applitude || this));
},{}],4:[function(_dereq_,module,exports){
var EvStore = _dereq_("ev-store")
module.exports = addEvent
function addEvent(target, type, handler) {
var events = EvStore(target)
var event = events[type]
if (!event) {
events[type] = handler
} else if (Array.isArray(event)) {
if (event.indexOf(handler) === -1) {
event.push(handler)
}
} else if (event !== handler) {
events[type] = [event, handler]
}
}
},{"ev-store":7}],5:[function(_dereq_,module,exports){
var globalDocument = _dereq_("global/document")
var EvStore = _dereq_("ev-store")
var createStore = _dereq_("weakmap-shim/create-store")
var addEvent = _dereq_("./add-event.js")
var removeEvent = _dereq_("./remove-event.js")
var ProxyEvent = _dereq_("./proxy-event.js")
var HANDLER_STORE = createStore()
module.exports = DOMDelegator
function DOMDelegator(document) {
if (!(this instanceof DOMDelegator)) {
return new DOMDelegator(document);
}
document = document || globalDocument
this.target = document.documentElement
this.events = {}
this.rawEventListeners = {}
this.globalListeners = {}
}
DOMDelegator.prototype.addEventListener = addEvent
DOMDelegator.prototype.removeEventListener = removeEvent
DOMDelegator.allocateHandle =
function allocateHandle(func) {
var handle = new Handle()
HANDLER_STORE(handle).func = func;
return handle
}
DOMDelegator.transformHandle =
function transformHandle(handle, broadcast) {
var func = HANDLER_STORE(handle).func
return this.allocateHandle(function (ev) {
broadcast(ev, func);
})
}
DOMDelegator.prototype.addGlobalEventListener =
function addGlobalEventListener(eventName, fn) {
var listeners = this.globalListeners[eventName] || [];
if (listeners.indexOf(fn) === -1) {
listeners.push(fn)
}
this.globalListeners[eventName] = listeners;
}
DOMDelegator.prototype.removeGlobalEventListener =
function removeGlobalEventListener(eventName, fn) {
var listeners = this.globalListeners[eventName] || [];
var index = listeners.indexOf(fn)
if (index !== -1) {
listeners.splice(index, 1)
}
}
DOMDelegator.prototype.listenTo = function listenTo(eventName) {
if (!(eventName in this.events)) {
this.events[eventName] = 0;
}
this.events[eventName]++;
if (this.events[eventName] !== 1) {
return
}
var listener = this.rawEventListeners[eventName]
if (!listener) {
listener = this.rawEventListeners[eventName] =
createHandler(eventName, this)
}
this.target.addEventListener(eventName, listener, true)
}
DOMDelegator.prototype.unlistenTo = function unlistenTo(eventName) {
if (!(eventName in this.events)) {
this.events[eventName] = 0;
}
if (this.events[eventName] === 0) {
throw new Error("already unlistened to event.");
}
this.events[eventName]--;
if (this.events[eventName] !== 0) {
return
}
var listener = this.rawEventListeners[eventName]
if (!listener) {
throw new Error("dom-delegator#unlistenTo: cannot " +
"unlisten to " + eventName)
}
this.target.removeEventListener(eventName, listener, true)
}
function createHandler(eventName, delegator) {
var globalListeners = delegator.globalListeners;
var delegatorTarget = delegator.target;
return handler
function handler(ev) {
var globalHandlers = globalListeners[eventName] || []
if (globalHandlers.length > 0) {
var globalEvent = new ProxyEvent(ev);
globalEvent.currentTarget = delegatorTarget;
callListeners(globalHandlers, globalEvent)
}
findAndInvokeListeners(ev.target, ev, eventName)
}
}
function findAndInvokeListeners(elem, ev, eventName) {
var listener = getListener(elem, eventName)
if (listener && listener.handlers.length > 0) {
var listenerEvent = new ProxyEvent(ev);
listenerEvent.currentTarget = listener.currentTarget
callListeners(listener.handlers, listenerEvent)
if (listenerEvent._bubbles) {
var nextTarget = listener.currentTarget.parentNode
findAndInvokeListeners(nextTarget, ev, eventName)
}
}
}
function getListener(target, type) {
// terminate recursion if parent is `null`
if (target === null) {
return null
}
var events = EvStore(target)
// fetch list of handler fns for this event
var handler = events[type]
var allHandler = events.event
if (!handler && !allHandler) {
return getListener(target.parentNode, type)
}
var handlers = [].concat(handler || [], allHandler || [])
return new Listener(target, handlers)
}
function callListeners(handlers, ev) {
handlers.forEach(function (handler) {
if (typeof handler === "function") {
handler(ev)
} else if (typeof handler.handleEvent === "function") {
handler.handleEvent(ev)
} else if (handler.type === "dom-delegator-handle") {
HANDLER_STORE(handler).func(ev)
} else {
throw new Error("dom-delegator: unknown handler " +
"found: " + JSON.stringify(handlers));
}
})
}
function Listener(target, handlers) {
this.currentTarget = target
this.handlers = handlers
}
function Handle() {
this.type = "dom-delegator-handle"
}
},{"./add-event.js":4,"./proxy-event.js":12,"./remove-event.js":13,"ev-store":7,"global/document":17,"weakmap-shim/create-store":85}],6:[function(_dereq_,module,exports){
var Individual = _dereq_("individual")
var cuid = _dereq_("cuid")
var globalDocument = _dereq_("global/document")
var DOMDelegator = _dereq_("./dom-delegator.js")
var versionKey = "13"
var cacheKey = "__DOM_DELEGATOR_CACHE@" + versionKey
var cacheTokenKey = "__DOM_DELEGATOR_CACHE_TOKEN@" + versionKey
var delegatorCache = Individual(cacheKey, {
delegators: {}
})
var commonEvents = [
"blur", "change", "click", "contextmenu", "dblclick",
"error","focus", "focusin", "focusout", "input", "keydown",
"keypress", "keyup", "load", "mousedown", "mouseup",
"resize", "select", "submit", "touchcancel",
"touchend", "touchstart", "unload"
]
/* Delegator is a thin wrapper around a singleton `DOMDelegator`
instance.
Only one DOMDelegator should exist because we do not want
duplicate event listeners bound to the DOM.
`Delegator` will also `listenTo()` all events unless
every caller opts out of it
*/
module.exports = Delegator
function Delegator(opts) {
opts = opts || {}
var document = opts.document || globalDocument
var cacheKey = document[cacheTokenKey]
if (!cacheKey) {
cacheKey =
document[cacheTokenKey] = cuid()
}
var delegator = delegatorCache.delegators[cacheKey]
if (!delegator) {
delegator = delegatorCache.delegators[cacheKey] =
new DOMDelegator(document)
}
if (opts.defaultEvents !== false) {
for (var i = 0; i < commonEvents.length; i++) {
delegator.listenTo(commonEvents[i])
}
}
return delegator
}
Delegator.allocateHandle = DOMDelegator.allocateHandle;
Delegator.transformHandle = DOMDelegator.transformHandle;
},{"./dom-delegator.js":5,"cuid":3,"global/document":17,"individual":10}],7:[function(_dereq_,module,exports){
'use strict';
var OneVersionConstraint = _dereq_('individual/one-version');
var MY_VERSION = '7';
OneVersionConstraint('ev-store', MY_VERSION);
var hashKey = '__EV_STORE_KEY@' + MY_VERSION;
module.exports = EvStore;
function EvStore(elem) {
var hash = elem[hashKey];
if (!hash) {
hash = elem[hashKey] = {};
}
return hash;
}
},{"individual/one-version":9}],8:[function(_dereq_,module,exports){
(function (global){
'use strict';
/*global window, global*/
var root = typeof window !== 'undefined' ?
window : typeof global !== 'undefined' ?
global : {};
module.exports = Individual;
function Individual(key, value) {
if (key in root) {
return root[key];
}
root[key] = value;
return value;
}
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],9:[function(_dereq_,module,exports){
'use strict';
var Individual = _dereq_('./index.js');
module.exports = OneVersion;
function OneVersion(moduleName, version, defaultValue) {
var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName;
var enforceKey = key + '_ENFORCE_SINGLETON';
var versionValue = Individual(enforceKey, version);
if (versionValue !== version) {
throw new Error('Can only have one copy of ' +
moduleName + '.\n' +
'You already have version ' + versionValue +
' installed.\n' +
'This means you cannot install version ' + version);
}
return Individual(key, defaultValue);
}
},{"./index.js":8}],10:[function(_dereq_,module,exports){
(function (global){
var root = typeof window !== 'undefined' ?
window : typeof global !== 'undefined' ?
global : {};
module.exports = Individual
function Individual(key, value) {
if (root[key]) {
return root[key]
}
Object.defineProperty(root, key, {
value: value
, configurable: true
})
return value
}
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],11:[function(_dereq_,module,exports){
if (typeof Object.create === 'function') {
// implementation from standard node.js 'util' module
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
} else {
// old school shim for old browsers
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
var TempCtor = function () {}
TempCtor.prototype = superCtor.prototype
ctor.prototype = new TempCtor()
ctor.prototype.constructor = ctor
}
}
},{}],12:[function(_dereq_,module,exports){
var inherits = _dereq_("inherits")
var ALL_PROPS = [
"altKey", "bubbles", "cancelable", "ctrlKey",
"eventPhase", "metaKey", "relatedTarget", "shiftKey",
"target", "timeStamp", "type", "view", "which"
]
var KEY_PROPS = ["char", "charCode", "key", "keyCode"]
var MOUSE_PROPS = [
"button", "buttons", "clientX", "clientY", "layerX",
"layerY", "offsetX", "offsetY", "pageX", "pageY",
"screenX", "screenY", "toElement"
]
var rkeyEvent = /^key|input/
var rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/
module.exports = ProxyEvent
function ProxyEvent(ev) {
if (!(this instanceof ProxyEvent)) {
return new ProxyEvent(ev)
}
if (rkeyEvent.test(ev.type)) {
return new KeyEvent(ev)
} else if (rmouseEvent.test(ev.type)) {
return new MouseEvent(ev)
}
for (var i = 0; i < ALL_PROPS.length; i++) {
var propKey = ALL_PROPS[i]
this[propKey] = ev[propKey]
}
this._rawEvent = ev
this._bubbles = false;
}
ProxyEvent.prototype.preventDefault = function () {
this._rawEvent.preventDefault()
}
ProxyEvent.prototype.startPropagation = function () {
this._bubbles = true;
}
function MouseEvent(ev) {
for (var i = 0; i < ALL_PROPS.length; i++) {
var propKey = ALL_PROPS[i]
this[propKey] = ev[propKey]
}
for (var j = 0; j < MOUSE_PROPS.length; j++) {
var mousePropKey = MOUSE_PROPS[j]
this[mousePropKey] = ev[mousePropKey]
}
this._rawEvent = ev
}
inherits(MouseEvent, ProxyEvent)
function KeyEvent(ev) {
for (var i = 0; i < ALL_PROPS.length; i++) {
var propKey = ALL_PROPS[i]
this[propKey] = ev[propKey]
}
for (var j = 0; j < KEY_PROPS.length; j++) {
var keyPropKey = KEY_PROPS[j]
this[keyPropKey] = ev[keyPropKey]
}
this._rawEvent = ev
}
inherits(KeyEvent, ProxyEvent)
},{"inherits":11}],13:[function(_dereq_,module,exports){
var EvStore = _dereq_("ev-store")
module.exports = removeEvent
function removeEvent(target, type, handler) {
var events = EvStore(target)
var event = events[type]
if (!event) {
return
} else if (Array.isArray(event)) {
var index = event.indexOf(handler)
if (index !== -1) {
event.splice(index, 1)
}
} else if (event === handler) {
events[type] = null
}
}
},{"ev-store":7}],14:[function(_dereq_,module,exports){
module.exports = Event
function Event() {
var listeners = []
return { broadcast: broadcast, listen: event }
function broadcast(value) {
for (var i = 0; i < listeners.length; i++) {
listeners[i](value)
}
}
function event(listener) {
listeners.push(listener)
return removeListener
function removeListener() {
var index = listeners.indexOf(listener)
if (index !== -1) {
listeners.splice(index, 1)
}
}
}
}
},{}],15:[function(_dereq_,module,exports){
var event = _dereq_("./single.js")
module.exports = multiple
function multiple(names) {
return names.reduce(function (acc, name) {
acc[name] = event()
return acc
}, {})
}
},{"./single.js":16}],16:[function(_dereq_,module,exports){
var Event = _dereq_('./event.js')
module.exports = Single
function Single() {
var tuple = Event()
return function event(value) {
if (typeof value === "function") {
return tuple.listen(value)
} else {
return tuple.broadcast(value)
}
}
}
},{"./event.js":14}],17:[function(_dereq_,module,exports){
(function (global){
var topLevel = typeof global !== 'undefined' ? global :
typeof window !== 'undefined' ? window : {}
var minDoc = _dereq_('min-document');
if (typeof document !== 'undefined') {
module.exports = document;
} else {
var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
if (!doccy) {
doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
}
module.exports = doccy;
}
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"min-document":2}],18:[function(_dereq_,module,exports){
var raf = _dereq_("raf")
var TypedError = _dereq_("error/typed")
var InvalidUpdateInRender = TypedError({
type: "main-loop.invalid.update.in-render",
message: "main-loop: Unexpected update occurred in loop.\n" +
"We are currently rendering a view, " +
"you can't change state right now.\n" +
"The diff is: {stringDiff}.\n" +
"SUGGESTED FIX: find the state mutation in your view " +
"or rendering function and remove it.\n" +
"The view should not have any side effects.\n",
diff: null,
stringDiff: null
})
module.exports = main
function main(initialState, view, opts) {
opts = opts || {}
var currentState = initialState
var create = opts.create
var diff = opts.diff
var patch = opts.patch
var redrawScheduled = false
var tree = opts.initialTree || view(currentState)
var target = opts.target || create(tree, opts)
var inRenderingTransaction = false
currentState = null
return {
target: target,
update: update
}
function update(state) {
if (inRenderingTransaction) {
throw InvalidUpdateInRender({
diff: state._diff,
stringDiff: JSON.stringify(state._diff)
})
}
if (currentState === null && !redrawScheduled) {
redrawScheduled = true
raf(redraw)
}
currentState = state
}
function redraw() {
redrawScheduled = false;
if (currentState === null) {
return
}
inRenderingTransaction = true
var newTree = view(currentState)
if (opts.createOnly) {
inRenderingTransaction = false
create(newTree, opts)
} else {
var patches = diff(tree, newTree, opts)
inRenderingTransaction = false
target = patch(target, patches, opts)
}
tree = newTree
currentState = null
}
}
},{"error/typed":21,"raf":39}],19:[function(_dereq_,module,exports){
module.exports = function(obj) {
if (typeof obj === 'string') return camelCase(obj);
return walk(obj);
};
function walk (obj) {
if (!obj || typeof obj !== 'object') return obj;
if (isDate(obj) || isRegex(obj)) return obj;
if (isArray(obj)) return map(obj, walk);
return reduce(objectKeys(obj), function (acc, key) {
var camel = camelCase(key);
acc[camel] = walk(obj[key]);
return acc;
}, {});
}
function camelCase(str) {
return str.replace(/[_.-](\w|$)/g, function (_,x) {
return x.toUpperCase();
});
}
var isArray = Array.isArray || function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
var isDate = function (obj) {
return Object.prototype.toString.call(obj) === '[object Date]';
};
var isRegex = function (obj) {
return Object.prototype.toString.call(obj) === '[object RegExp]';
};
var has = Object.prototype.hasOwnProperty;
var objectKeys = Object.keys || function (obj) {
var keys = [];
for (var key in obj) {
if (has.call(obj, key)) keys.push(key);
}
return keys;
};
function map (xs, f) {
if (xs.map) return xs.map(f);
var res = [];
for (var i = 0; i < xs.length; i++) {
res.push(f(xs[i], i));
}
return res;
}
function reduce (xs, f, acc) {
if (xs.reduce) return xs.reduce(f, acc);
for (var i = 0; i < xs.length; i++) {
acc = f(acc, xs[i], i);
}
return acc;
}
},{}],20:[function(_dereq_,module,exports){
var nargs = /\{([0-9a-zA-Z]+)\}/g
var slice = Array.prototype.slice
module.exports = template
function template(string) {
var args
if (arguments.length === 2 && typeof arguments[1] === "object") {
args = arguments[1]
} else {
args = slice.call(arguments, 1)
}
if (!args || !args.hasOwnProperty) {
args = {}
}
return string.replace(nargs, function replaceArg(match, i, index) {
var result
if (string[index - 1] === "{" &&
string[index + match.length] === "}") {
return i
} else {
result = args.hasOwnProperty(i) ? args[i] : null
if (result === null || result === undefined) {
return ""
}
return result
}
})
}
},{}],21:[function(_dereq_,module,exports){
var camelize = _dereq_("camelize")
var template = _dereq_("string-template")
var extend = _dereq_("xtend/mutable")
module.exports = TypedError
function TypedError(args) {
if (!args) {
throw new Error("args is required");
}
if (!args.type) {
throw new Error("args.type is required");
}
if (!args.message) {
throw new Error("args.message is required");
}
var message = args.message
if (args.type && !args.name) {
var errorName = camelize(args.type) + "Error"
args.name = errorName[0].toUpperCase() + errorName.substr(1)
}
extend(createError, args);
createError._name = args.name;
return createError;
function createError(opts) {
var result = new Error()
Object.defineProperty(result, "type", {
value: result.type,
enumerable: true,
writable: true,
configurable: true
})
var options = extend({}, args, opts)
extend(result, options)
result.message = template(message, options)
return result
}
}
},{"camelize":19,"string-template":20,"xtend/mutable":88}],22:[function(_dereq_,module,exports){
var setNonEnumerable = _dereq_("./lib/set-non-enumerable.js");
module.exports = addListener
function addListener(observArray, observ) {
var list = observArray._list
return observ(function (value) {
var valueList = observArray().slice()
var index = list.indexOf(observ)
// This code path should never hit. If this happens
// there's a bug in the cleanup code
if (index === -1) {
var message = "observ-array: Unremoved observ listener"
var err = new Error(message)
err.list = list
err.index = index
err.observ = observ
throw err
}
valueList.splice(index, 1, value)
setNonEnumerable(valueList, "_diff", [ [index, 1, value] ])
observArray._observSet(valueList)
})
}
},{"./lib/set-non-enumerable.js":25}],23:[function(_dereq_,module,exports){
var ObservArray = _dereq_("./index.js")
var slice = Array.prototype.slice
var ARRAY_METHODS = [
"concat", "slice", "every", "filter", "forEach", "indexOf",
"join", "lastIndexOf", "map", "reduce", "reduceRight",
"some", "toString", "toLocaleString"
]
var methods = ARRAY_METHODS.map(function (name) {
return [name, function () {
var res = this._list[name].apply(this._list, arguments)
if (res && Array.isArray(res)) {
res = ObservArray(res)
}
return res
}]
})
module.exports = ArrayMethods
function ArrayMethods(obs) {
obs.push = observArrayPush
obs.pop = observArrayPop
obs.shift = observArrayShift
obs.unshift = observArrayUnshift
obs.reverse = notImplemented
obs.sort = notImplemented
methods.forEach(function (tuple) {
obs[tuple[0]] = tuple[1]
})
return obs
}
function observArrayPush() {
var args = slice.call(arguments)
args.unshift(this._list.length, 0)
this.splice.apply(this, args)
return this._list.length
}
function observArrayPop() {
return this.splice(this._list.length - 1, 1)[0]
}
function observArrayShift() {
return this.splice(0, 1)[0]
}
function observArrayUnshift() {
var args = slice.call(arguments)
args.unshift(0, 0)
this.splice.apply(this, args)
return this._list.length
}
function notImplemented() {
throw new Error("Pull request welcome")
}
},{"./index.js":24}],24:[function(_dereq_,module,exports){
var Observ = _dereq_("observ")
// circular dep between ArrayMethods & this file
module.exports = ObservArray
var splice = _dereq_("./splice.js")
var put = _dereq_("./put.js")
var set = _dereq_("./set.js")
var transaction = _dereq_("./transaction.js")
var ArrayMethods = _dereq_("./array-methods.js")
var addListener = _dereq_("./add-listener.js")
/* ObservArray := (Array<T>) => Observ<
Array<T> & { _diff: Array }
> & {
splice: (index: Number, amount: Number, rest...: T) =>
Array<T>,
push: (values...: T) => Number,
filter: (lambda: Function, thisValue: Any) => Array<T>,
indexOf: (item: T, fromIndex: Number) => Number
}
Fix to make it more like ObservHash.
I.e. you write observables into it.
reading methods take plain JS objects to read
and the value of the array is always an array of plain
objsect.
The observ array instance itself would have indexed
properties that are the observables
*/
function ObservArray(initialList) {
// list is the internal mutable list observ instances that
// all methods on `obs` dispatch to.
var list = initialList
var initialState = []
// copy state out of initialList into initialState
list.forEach(function (observ, index) {
initialState[index] = typeof observ === "function" ?
observ() : observ
})
var obs = Observ(initialState)
obs.splice = splice
// override set and store original for later use
obs._observSet = obs.set
obs.set = set
obs.get = get
obs.getLength = getLength
obs.put = put
obs.transaction = transaction
// you better not mutate this list directly
// this is the list of observs instances
obs._list = list
var removeListeners = list.map(function (observ) {
return typeof observ === "function" ?
addListener(obs, observ) :
null
});
// this is a list of removal functions that must be called
// when observ instances are removed from `obs.list`
// not calling this means we do not GC our observ change
// listeners. Which causes rage bugs
obs._removeListeners = removeListeners
obs._type = "observ-array"
obs._version = "3"
return ArrayMethods(obs, list)
}
function get(index) {
return this._list[index]
}
function getLength() {
return this._list.length
}
},{"./add-listener.js":22,"./array-methods.js":23,"./put.js":27,"./set.js":28,"./splice.js":29,"./transaction.js":30,"observ":36}],25:[function(_dereq_,module,exports){
module.exports = setNonEnumerable;
function setNonEnumerable(object, key, value) {
Object.defineProperty(object, key, {
value: value,
writable: true,
configurable: true,
enumerable: false
});
}
},{}],26:[function(_dereq_,module,exports){
function head (a) {
return a[0]
}
function last (a) {
return a[a.length - 1]
}
function tail(a) {
return a.slice(1)
}
function retreat (e) {
return e.pop()
}
function hasLength (e) {
return e.length
}
function any(ary, test) {
for(var i=0;i<ary.length;i++)
if(test(ary[i]))
return true
return false
}
function score (a) {
return a.reduce(function (s, a) {
return s + a.length + a[1] + 1
}, 0)
}
function best (a, b) {
return score(a) <= score(b) ? a : b
}
var _rules // set at the bottom
// note, naive implementation. will break on circular objects.
function _equal(a, b) {
if(a && !b) return false
if(Array.isArray(a))
if(a.length != b.length) return false
if(a && 'object' == typeof a) {
for(var i in a)
if(!_equal(a[i], b[i])) return false
for(var i in b)
if(!_equal(a[i], b[i])) return false
return true
}
return a == b
}
function getArgs(args) {
return args.length == 1 ? args[0] : [].slice.call(args)
}
// return the index of the element not like the others, or -1
function oddElement(ary, cmp) {
var c
function guess(a) {
var odd = -1
c = 0
for (var i = a; i < ary.length; i ++) {
if(!cmp(ary[a], ary[i])) {
odd = i, c++
}
}
return c > 1 ? -1 : odd
}
//assume that it is the first element.
var g = guess(0)
if(-1 != g) return g
//0 was the odd one, then all the other elements are equal
//else there more than one different element
guess(1)
return c == 0 ? 0 : -1
}
var exports = module.exports = function (deps, exports) {
var equal = (deps && deps.equal) || _equal
exports = exports || {}
exports.lcs =
function lcs() {
var cache = {}
var args = getArgs(arguments)
var a = args[0], b = args[1]
function key (a,b){
return a.length + ':' + b.length
}
//find length that matches at the head
if(args.length > 2) {
//if called with multiple sequences
//recurse, since lcs(a, b, c, d) == lcs(lcs(a,b), lcs(c,d))
args.push(lcs(args.shift(), args.shift()))
return lcs(args)
}
//this would be improved by truncating input first
//and not returning an lcs as an intermediate step.
//untill that is a performance problem.
var start = 0, end = 0
for(var i = 0; i < a.length && i < b.length
&& equal(a[i], b[i])
; i ++
)
start = i + 1
if(a.length === start)
return a.slice()
for(var i = 0; i < a.length - start && i < b.length - start
&& equal(a[a.length - 1 - i], b[b.length - 1 - i])
; i ++
)
end = i
function recurse (a, b) {
if(!a.length || !b.length) return []
//avoid exponential time by caching the results
if(cache[key(a, b)]) return cache[key(a, b)]
if(equal(a[0], b[0]))
return [head(a)].concat(recurse(tail(a), tail(b)))
else {
var _a = recurse(tail(a), b)
var _b = recurse(a, tail(b))
return cache[key(a,b)] = _a.length > _b.length ? _a : _b
}
}
var middleA = a.slice(start, a.length - end)
var middleB = b.slice(start, b.length - end)
return (
a.slice(0, start).concat(
recurse(middleA, middleB)
).concat(a.slice(a.length - end))
)
}
// given n sequences, calc the lcs, and then chunk strings into stable and unstable sections.
// unstable chunks are passed to build
exports.chunk =
function (q, build) {
var q = q.map(function (e) { return e.slice() })
var lcs = exports.lcs.apply(null, q)
var all = [lcs].concat(q)
function matchLcs (e) {
if(e.length && !lcs.length || !e.length && lcs.length)
return false //incase the last item is null
return equal(last(e), last(lcs)) || ((e.length + lcs.length) === 0)
}
while(any(q, hasLength)) {
//if each element is at the lcs then this chunk is stable.
while(q.every(matchLcs) && q.every(hasLength))
all.forEach(retreat)
//collect the changes in each array upto the next match with the lcs
var c = false
var unstable = q.map(function (e) {
var change = []
while(!matchLcs(e)) {
change.unshift(retreat(e))
c = true
}
return change
})
if(c) build(q[0].length, unstable)
}
}
//calculate a diff this is only updates
exports.optimisticDiff =
function (a, b) {
var M = Math.max(a.length, b.length)
var m = Math.min(a.length, b.length)
var patch = []
for(var i = 0; i < M; i++)
if(a[i] !== b[i]) {
var cur = [i,0], deletes = 0
while(a[i] !== b[i] && i < m) {
cur[1] = ++deletes
cur.push(b[i++])
}
//the rest are deletes or inserts
if(i >= m) {
//the rest are deletes
if(a.length > b.length)
cur[1] += a.length - b.length
//the rest are inserts
else if(a.length < b.length)
cur = cur.concat(b.slice(a.length))
}
patch.push(cur)
}
return patch
}
exports.diff =
function (a, b) {
var optimistic = exports.optimisticDiff(a, b)
var changes = []
exports.chunk([a, b], function (index, unstable) {
var del = unstable.shift().length
var insert = unstable.shift()
changes.push([index, del].concat(insert))
})
return best(optimistic, changes)
}
exports.patch = function (a, changes, mutate) {
if(mutate !== true) a = a.slice(a)//copy a
changes.forEach(function (change) {
[].splice.apply(a, change)
})
return a
}
// http://en.wikipedia.org/wiki/Concestor
// me, concestor, you...
exports.merge = function () {
var args = getArgs(arguments)
var patch = exports.diff3(args)
return exports.patch(args[0], patch)
}
exports.diff3 = function () {
var args = getArgs(arguments)
var r = []
exports.chunk(args, function (index, unstable) {
var mine = unstable[0]
var insert = resolve(unstable)
if(equal(mine, insert)) return
r.push([index, mine.length].concat(insert))
})
return r
}
exports.oddOneOut =
function oddOneOut (changes) {
changes = changes.slice()
//put the concestor first
changes.unshift(changes.splice(1,1)[0])
var i = oddElement(changes, equal)
if(i == 0) // concestor was different, 'false conflict'
return changes[1]
if (~i)
return changes[i]
}
exports.insertMergeOverDelete =
//i've implemented this as a seperate rule,
//because I had second thoughts about this.
function insertMergeOverDelete (changes) {
changes = changes.slice()
changes.splice(1,1)// remove concestor
//if there is only one non empty change thats okay.
//else full confilct
for (var i = 0, nonempty; i < changes.length; i++)
if(changes[i].length)
if(!nonempty) nonempty = changes[i]
else return // full conflict
return nonempty
}
var rules = (deps && deps.rules) || [exports.oddOneOut, exports.insertMergeOverDelete]
function resolve (changes) {
var l = rules.length
for (var i in rules) { // first
var c = rules[i] && rules[i](changes)
if(c) return c
}
changes.splice(1,1) // remove concestor
//returning the conflicts as an object is a really bad idea,
// because == will not detect they are the same. and conflicts build.
// better to use
// '<<<<<<<<<<<<<'
// of course, i wrote this before i started on snob, so i didn't know that then.
/*var conflict = ['>>>>>>>>>>>>>>>>']
while(changes.length)
conflict = conflict.concat(changes.shift()).concat('============')
conflict.pop()
conflict.push ('<<<<<<<<<<<<<<<')
changes.unshift ('>>>>>>>>>>>>>>>')
return conflict*/
//nah, better is just to use an equal can handle objects
return {'?': changes}
}
return exports
}
exports(null, exports)
},{}],27:[function(_dereq_,module,exports){
var addListener = _dereq_("./add-listener.js")
var setNonEnumerable = _dereq_("./lib/set-non-enumerable.js");
module.exports = put
// `obs.put` is a mutable implementation of `array[index] = value`
// that mutates both `list` and the internal `valueList` that
// is the current value of `obs` itself
function put(index, value) {
var obs = this
var valueList = obs().slice()
var originalLength = valueList.length
valueList[index] = typeof value === "function" ? value() : value
obs._list[index] = value
// remove past value listener if was observ
var removeListener = obs._removeListeners[index]
if (removeListener){
removeListener()
}
// add listener to value if observ
obs._removeListeners[index] = typeof value === "function" ?
addListener(obs, value) :
null
// fake splice diff
var valueArgs = index < originalLength ?
[index, 1, valueList[index]] :
[index, 0, valueList[index]]
setNonEnumerable(valueList, "_diff", [valueArgs])
obs._observSet(valueList)
return value
}
},{"./add-listener.js":22,"./lib/set-non-enumerable.js":25}],28:[function(_dereq_,module,exports){
var addListener = _dereq_("./add-listener.js")
var setNonEnumerable = _dereq_("./lib/set-non-enumerable.js")
var adiff = _dereq_("adiff")
module.exports = set
function set(rawList) {
if (!Array.isArray(rawList)) rawList = []
var obs = this
var changes = adiff.diff(obs._list, rawList)
var valueList = obs().slice()
var valueChanges = changes.map(applyPatch.bind(obs, valueList))
setNonEnumerable(valueList, "_diff", valueChanges)
obs._observSet(valueList)
return changes
}
function applyPatch (valueList, args) {
var obs = this
var valueArgs = args.map(unpack)
valueList.splice.apply(valueList, valueArgs)
obs._list.splice.apply(obs._list, args)
var extraRemoveListeners = args.slice(2).map(function (observ) {
return typeof observ === "function" ?
addListener(obs, observ) :
null
})
extraRemoveListeners.unshift(args[0], args[1])
var removedListeners = obs._removeListeners.splice
.apply(obs._removeListeners, extraRemoveListeners)
removedListeners.forEach(function (removeObservListener) {
if (removeObservListener) {
removeObservListener()
}
})
return valueArgs
}
function unpack(value, index){
if (index === 0 || index === 1) {
return value
}
return typeof value === "function" ? value() : value
}
},{"./add-listener.js":22,"./lib/set-non-enumerable.js":25,"adiff":26}],29:[function(_dereq_,module,exports){
var slice = Array.prototype.slice
var addListener = _dereq_("./add-listener.js")
var setNonEnumerable = _dereq_("./lib/set-non-enumerable.js");
module.exports = splice
// `obs.splice` is a mutable implementation of `splice()`
// that mutates both `list` and the internal `valueList` that
// is the current value of `obs` itself
function splice(index, amount) {
var obs = this
var args = slice.call(arguments, 0)
var valueList = obs().slice()
// generate a list of args to mutate the internal
// list of only obs
var valueArgs = args.map(function (value, index) {
if (index === 0 || index === 1) {
return value
}
// must unpack observables that we are adding
return typeof value === "function" ? value() : value
})
valueList.splice.apply(valueList, valueArgs)
// we remove the observs that we remove
var removed = obs._list.splice.apply(obs._list, args)
var extraRemoveListeners = args.slice(2).map(function (observ) {
return typeof observ === "function" ?
addListener(obs, observ) :
null
})
extraRemoveListeners.unshift(args[0], args[1])
var removedListeners = obs._removeListeners.splice
.apply(obs._removeListeners, extraRemoveListeners)
removedListeners.forEach(function (removeObservListener) {
if (removeObservListener) {
removeObservListener()
}
})
setNonEnumerable(valueList, "_diff", [valueArgs])
obs._observSet(valueList)
return removed
}
},{"./add-listener.js":22,"./lib/set-non-enumerable.js":25}],30:[function(_dereq_,module,exports){
module.exports = transaction
function transaction (func) {
var obs = this
var rawList = obs._list.slice()
if (func(rawList) !== false){ // allow cancel
return obs.set(rawList)
}
}
},{}],31:[function(_dereq_,module,exports){
var Observ = _dereq_("observ")
var extend = _dereq_("xtend")
var blackList = ["name", "_diff", "_type", "_version"]
var blackListReasons = {
"name": "Clashes with `Function.prototype.name`.\n",
"_diff": "_diff is reserved key of observ-struct.\n",
"_type": "_type is reserved key of observ-struct.\n",
"_version": "_version is reserved key of observ-struct.\n"
}
var NO_TRANSACTION = {}
function setNonEnumerable(object, key, value) {
Object.defineProperty(object, key, {
value: value,
writable: true,
configurable: true,
enumerable: false
})
}
/* ObservStruct := (Object<String, Observ<T>>) =>
Object<String, Observ<T>> &
Observ<Object<String, T> & {
_diff: Object<String, Any>
}>
*/
module.exports = ObservStruct
function ObservStruct(struct) {
var keys = Object.keys(struct)
var initialState = {}
var currentTransaction = NO_TRANSACTION
var nestedTransaction = NO_TRANSACTION
keys.forEach(function (key) {
if (blackList.indexOf(key) !== -1) {
throw new Error("cannot create an observ-struct " +
"with a key named '" + key + "'.\n" +
blackListReasons[key]);
}
var observ = struct[key]
initialState[key] = typeof observ === "function" ?
observ() : observ
})
var obs = Observ(initialState)
keys.forEach(function (key) {
var observ = struct[key]
obs[key] = observ
if (typeof observ === "function") {
observ(function (value) {
if (nestedTransaction === value) {
return
}
var state = extend(obs())
state[key] = value
var diff = {}
diff[key] = value && value._diff ?
value._diff : value
setNonEnumerable(state, "_diff", diff)
currentTransaction = state
obs.set(state)
currentTransaction = NO_TRANSACTION
})
}
})
var _set = obs.set
obs.set = function trackDiff(value) {
if (currentTransaction === value) {
return _set(value)
}
var newState = extend(value)
setNonEnumerable(newState, "_diff", value)
_set(newState)
}
obs(function (newState) {
if (currentTransaction === newState) {
return
}
keys.forEach(function (key) {
var observ = struct[key]
var newObservValue = newState[key]
if (typeof observ === "function" &&
observ() !== newObservValue
) {
nestedTransaction = newObservValue
observ.set(newState[key])
nestedTransaction = NO_TRANSACTION
}
})
})
obs._type = "observ-struct"
obs._version = "5"
return obs
}
},{"observ":36,"xtend":32}],32:[function(_dereq_,module,exports){
module.exports = extend
function extend() {
var target = {}
for (var i = 0; i < arguments.length; i++) {
var source = arguments[i]
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key]
}
}
}
return target
}
},{}],33:[function(_dereq_,module,exports){
var Observ = _dereq_('observ')
var extend = _dereq_('xtend')
var NO_TRANSACTION = {}
module.exports = ObservVarhash
function ObservVarhash (hash, createValue) {
createValue = createValue || function (obj) { return obj }
var initialState = {}
var currentTransaction = NO_TRANSACTION
var obs = Observ(initialState)
setNonEnumerable(obs, '_removeListeners', {})
setNonEnumerable(obs, 'set', obs.set)
setNonEnumerable(obs, 'get', get.bind(obs))
setNonEnumerable(obs, 'put', put.bind(obs, createValue))
setNonEnumerable(obs, 'delete', del.bind(obs))
for (var key in hash) {
obs[key] = typeof hash[key] === 'function' ?
hash[key] : createValue(hash[key], key)
if (isFn(obs[key])) {
obs._removeListeners[key] = obs[key](watch(obs, key, currentTransaction))
}
}
var newState = {}
for (key in hash) {
var observ = obs[key]
checkKey(key)
newState[key] = isFn(observ) ? ob