UNPKG

mercury

Version:

A truly modular frontend framework

1,847 lines (1,482 loc) 110 kB
// 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