brave
Version:
Old school web application library
1,939 lines (1,640 loc) • 144 kB
JavaScript
(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);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.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(require,module,exports){
module.exports = function anonymous(obj
/**/) {
var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('<form as="form"> <div errors> </div> <label id="name"><span class="label-text">Name: </span><input name="name" value="', data.name ,'" placeholder="Name" required><span class="indicator">*</span></label> <label id="email"><span class="label-text">Email: </span><input name="email" value="', data.email ,'" placeholder="Email" required><span class="indicator">*</span></label> <label id="question"><span class="label-text">Question: </span><input name="question" value="', data.question ,'" placeholder="Question" required><span class="indicator">*</span></label> <label id="line1"><span class="label-text">Line 1: </span><input name="line1" value="', data.line1 ,'" placeholder="Line1" required><span class="indicator">*</span></label> <label id="line2"><span class="label-text">Line 2: </span><input name="line2" value="', data.line2 ,'" placeholder="Line2" required><span class="indicator">*</span></label> <label id="postcode"><span class="label-text">Postcode: </span><input name="postcode" value="', data.postcode ,'" placeholder="Postcode" required><span class="indicator">*</span></label> <label id="name"><span class="label-text">Full Address: </span><address address></address></label> <button as="sendButton" type="submit" ', data.errors.length ? 'disabled' : '' ,'>Send</button> </form> ');}return p.join('');
};
},{}],2:[function(require,module,exports){
module.exports = function anonymous(obj
/**/) {
var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('<ul style="display: ', obj.length ? '' : 'none' ,'"> '); for (var i = 0; i < obj.length; i++) { p.push(' <li><a href="#', obj[i].key ,'">', obj[i].error ,'</a></li> '); } p.push(' </ul> ');}return p.join('');
};
},{}],3:[function(require,module,exports){
var Dom = require('../..')
var supermodels = require('supermodels.js')
var field = function (name) {
return {
__type: String,
__validators: [function (val) {
return val ? '' : (name + ' is required')
}]
}
}
var ContactUs = supermodels({
name: field('Name'),
question: field('Question'),
email: field('Email'),
line1: field('Line 1'),
line2: field('Line 2'),
postcode: field('Postcode'),
get fullAddress () {
return (this.line1 || '') + ' ' + (this.line2 || '') + ' ' + (this.postcode || '')
}
})
Dom.register({
'contact-us': {
initialize: function () {
console.log('initialize')
},
on: {
'change:form input': function (e) {
var el = e.target
this.data[el.name] = el.value
this.setButtonState()
}
},
setButtonState: function () {
this.sendButton.disabled = !!this.data.errors.length
},
template: require('./_contactus.html')
},
'address': {
initialize: function () {
var self = this
this.data.on('change', function () {
self.render()
})
},
template: function (model) {
return model.data.fullAddress
}
},
'errors': {
initialize: function () {
var self = this
this.data.on('change', function (e) {
self.render()
})
},
template: function () {
var errors = this.data.errors
return require('./_errors.html')(errors)
}
}
})
window.onload = function () {
var model = {
contactUs: new ContactUs()
}
Dom.scan(document.body, model)
}
},{"../..":4,"./_contactus.html":1,"./_errors.html":2,"supermodels.js":8}],4:[function(require,module,exports){
var get = require('get-object-path')
var Delegate = require('dom-delegate').Delegate
var onEvents = Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(window))).filter(function (i) {return !i.indexOf('on') && (document[i] == null || typeof document[i] == 'function');}).filter(function (elem, pos, self) {return self.indexOf(elem) == pos;})
var onEventsSelector = onEvents.map(function (key) {
return '[' + key + ']:not([' + key + '=""])'
}).join(',')
function Register () {}
Object.defineProperties(Register.prototype, {
selector: {
get: function () {
var keys = Object.keys(this)
return keys.map(function (key) {
return '[' + key + ']'
}).join(', ')
}
},
keys: {
get: function () {
return Object.keys(this)
}
}
})
var register = new Register()
function createContext (el, data, component, parent) {
var ctx = Object.create(component.isolate ? {} : parent || {})
var info = Object.create({}, {
component: {
value: component
}
})
Object.defineProperties(ctx, {
__: {
value: info
},
el: {
value: el
}
})
ctx.data = data
return ctx
}
var ignore = ['on', 'template', 'initialize', 'isolate']
function extend (obj) {
Array.prototype.slice.call(arguments, 1).forEach(function (source) {
var descriptor, prop
if (source) {
for (prop in source) {
if (source.hasOwnProperty(prop) && ignore.indexOf(prop) === -1) {
descriptor = Object.getOwnPropertyDescriptor(source, prop)
Object.defineProperty(obj, prop, descriptor)
}
}
}
})
return obj
}
function getElementComponent (el) {
var registerKeys = register.keys
for (var i = 0; i < el.attributes.length; i++) {
var idx = registerKeys.indexOf(el.attributes[i].name)
if (idx > -1) {
return {
key: registerKeys[idx],
component: register[registerKeys[idx]]
}
}
}
}
function createElementDelegate (el, ctx, component) {
var del = new Delegate(el)
// Add event listeners
var proxy = function (fn) {
return function (e) {
fn.call(ctx, e)
}
}
for (var event in component.on) {
if (component.on.hasOwnProperty(event)) {
var colon = event.indexOf(':')
var name, selector
if (colon === -1) {
name = event
del.on(name, proxy(component.on[event]))
} else {
name = event.substr(0, colon)
selector = event.substr(colon + 1)
del.on(name, selector, proxy(component.on[event]))
}
}
}
return del
}
function getElementData (el, componentName, parent) {
var attr = el.getAttribute(componentName)
return attr && get(parent, attr)
}
function registerComponent (name, obj) {
if (typeof name === 'object') {
for (var key in name) {
if (name.hasOwnProperty(key)) {
register[key] = name[key]
}
}
} else {
register[name] = obj
}
}
function nodeListToArray (nodeList) {
var nodeArray = []
for (var i = 0; i < nodeList.length; i++) {
nodeArray.push(nodeList[i])
}
return nodeArray
}
function getMatchingElements (el, childrenOnly) {
var selector = Dom._register.selector
var matches = nodeListToArray(el.querySelectorAll(selector))
if (!childrenOnly) {
var component = getElementComponent(el)
if (component) {
matches.unshift(el)
}
}
return matches
}
function findParentContext (el, contexts) {
do {
el = el.parentNode
if (el) {
for (var i = contexts.length - 1; i > -1; i--) {
if (contexts[i].ctx.el === el) {
return contexts[i].ctx
}
}
}
} while (el)
}
function setHtml (el, component, ctx) {
var html = (typeof component.template === 'function')
? component.template.call(ctx, ctx)
: component.template
el.innerHTML = html
}
function renderer (currEl, component, ctx) {
return function () {
setHtml(currEl, component, ctx)
Dom.scan(currEl, ctx.data, ctx, true)
}
}
function scan (el, data, parent, childrenOnly) {
var matches = getMatchingElements(el, childrenOnly)
var contexts = []
if (parent) {
contexts.push({ctx: parent})
}
var currEl
while (matches.length) {
currEl = matches.shift()
var ref = getElementComponent(currEl)
var component = ref.component
var parentContext = findParentContext(currEl, contexts) || parent
var parentData = parentContext ? parentContext.data : data
var elData = getElementData(currEl, ref.key, parentData) || parentData
var ctx = createContext(currEl, elData, component, parentContext)
var del = createElementDelegate(currEl, ctx, component)
Object.defineProperty(ctx.__, 'del', { value: del })
extend(ctx, component)
contexts.push({
key: ref.key, ctx: ctx, initialize: component.initialize,
template: component.template, component: component, el: currEl
})
}
var i, j
var processed = []
for (i = contexts.length - 1; i >= 0; i--) {
var aliasContext = contexts[i].ctx
var aliasEl = aliasContext.el
var aliases = aliasEl.querySelectorAll('[as]:not([as=""])')
for (j = 0; j < aliases.length; j++) {
if (processed.indexOf(aliases[j]) < 0) {
var attr = aliases[j].getAttribute('as')
aliasContext[attr] = aliases[j]
processed.push(aliases[j])
}
}
}
// processed = []
// for (i = contexts.length - 1; i >= 0; i--) {
// var onContext = contexts[i].ctx
// var onEl = onContext.el
// var ons = onEl.querySelectorAll('[onclick]:not([onclick=""])')
// for (j = 0; j < ons.length; j++) {
// if (processed.indexOf(ons[j]) < 0) {
// attr = ons[j].getAttribute('onclick')
// // var fn = ons[j].onclick
// var fn = new Function('with (this) {\n\treturn ' + attr + '\n}')
// ons[j].onclick = fn.bind(onContext)
// processed.push(ons[j])
// }
// }
// }
processed = []
for (i = contexts.length - 1; i >= 0; i--) {
var onContext = contexts[i].ctx
var onEl = onContext.el
var ons = nodeListToArray(onEl.querySelectorAll(onEventsSelector))
ons.unshift(onEl)
for (j = 0; j < ons.length; j++) {
if (processed.indexOf(ons[j]) < 0) {
processed.push(ons[j])
for (var k = 0; k < onEvents.length; k++) {
if (ons[j].attributes[onEvents[k]]) {
attr = ons[j].attributes[onEvents[k]].value
// var fn = ons[j].onclick
// var fn = new Function('e', 'with (this) {\n\treturn ' + attr + '\n}')
// ons[j][onEvents[k]] = fn.bind(onContext)
function handler (fn, ctx) {
return function (e) {
return fn.call(this, e, ctx)
}
}
var fn = new Function('e, ctx', 'with (ctx) {\n\treturn ' + attr + '\n}')
ons[j][onEvents[k]] = handler(fn, onContext)
}
}
}
}
}
for (i = 0; i < contexts.length; i++) {
if (contexts[i].initialize) {
contexts[i].initialize.call(contexts[i].ctx)
}
}
for (i = 0; i < contexts.length; i++) {
if (contexts[i].template) {
var render = renderer(contexts[i].ctx.el, contexts[i].component, contexts[i].ctx)
render()
contexts[i].ctx.render = render
}
}
}
var Dom = Object.create({}, {
_register: { value: register },
register: { value: registerComponent },
scan: { value: scan }
})
module.exports = Dom
},{"dom-delegate":6,"get-object-path":7}],5:[function(require,module,exports){
/*jshint browser:true, node:true*/
'use strict';
module.exports = Delegate;
/**
* DOM event delegator
*
* The delegator will listen
* for events that bubble up
* to the root node.
*
* @constructor
* @param {Node|string} [root] The root node or a selector string matching the root node
*/
function Delegate(root) {
/**
* Maintain a map of listener
* lists, keyed by event name.
*
* @type Object
*/
this.listenerMap = [{}, {}];
if (root) {
this.root(root);
}
/** @type function() */
this.handle = Delegate.prototype.handle.bind(this);
}
/**
* Start listening for events
* on the provided DOM element
*
* @param {Node|string} [root] The root node or a selector string matching the root node
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.root = function(root) {
var listenerMap = this.listenerMap;
var eventType;
// Remove master event listeners
if (this.rootElement) {
for (eventType in listenerMap[1]) {
if (listenerMap[1].hasOwnProperty(eventType)) {
this.rootElement.removeEventListener(eventType, this.handle, true);
}
}
for (eventType in listenerMap[0]) {
if (listenerMap[0].hasOwnProperty(eventType)) {
this.rootElement.removeEventListener(eventType, this.handle, false);
}
}
}
// If no root or root is not
// a dom node, then remove internal
// root reference and exit here
if (!root || !root.addEventListener) {
if (this.rootElement) {
delete this.rootElement;
}
return this;
}
/**
* The root node at which
* listeners are attached.
*
* @type Node
*/
this.rootElement = root;
// Set up master event listeners
for (eventType in listenerMap[1]) {
if (listenerMap[1].hasOwnProperty(eventType)) {
this.rootElement.addEventListener(eventType, this.handle, true);
}
}
for (eventType in listenerMap[0]) {
if (listenerMap[0].hasOwnProperty(eventType)) {
this.rootElement.addEventListener(eventType, this.handle, false);
}
}
return this;
};
/**
* @param {string} eventType
* @returns boolean
*/
Delegate.prototype.captureForType = function(eventType) {
return ['blur', 'error', 'focus', 'load', 'resize', 'scroll'].indexOf(eventType) !== -1;
};
/**
* Attach a handler to one
* event for all elements
* that match the selector,
* now or in the future
*
* The handler function receives
* three arguments: the DOM event
* object, the node that matched
* the selector while the event
* was bubbling and a reference
* to itself. Within the handler,
* 'this' is equal to the second
* argument.
*
* The node that actually received
* the event can be accessed via
* 'event.target'.
*
* @param {string} eventType Listen for these events
* @param {string|undefined} selector Only handle events on elements matching this selector, if undefined match root element
* @param {function()} handler Handler function - event data passed here will be in event.data
* @param {Object} [eventData] Data to pass in event.data
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.on = function(eventType, selector, handler, useCapture) {
var root, listenerMap, matcher, matcherParam;
if (!eventType) {
throw new TypeError('Invalid event type: ' + eventType);
}
// handler can be passed as
// the second or third argument
if (typeof selector === 'function') {
useCapture = handler;
handler = selector;
selector = null;
}
// Fallback to sensible defaults
// if useCapture not set
if (useCapture === undefined) {
useCapture = this.captureForType(eventType);
}
if (typeof handler !== 'function') {
throw new TypeError('Handler must be a type of Function');
}
root = this.rootElement;
listenerMap = this.listenerMap[useCapture ? 1 : 0];
// Add master handler for type if not created yet
if (!listenerMap[eventType]) {
if (root) {
root.addEventListener(eventType, this.handle, useCapture);
}
listenerMap[eventType] = [];
}
if (!selector) {
matcherParam = null;
// COMPLEX - matchesRoot needs to have access to
// this.rootElement, so bind the function to this.
matcher = matchesRoot.bind(this);
// Compile a matcher for the given selector
} else if (/^[a-z]+$/i.test(selector)) {
matcherParam = selector;
matcher = matchesTag;
} else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
matcherParam = selector.slice(1);
matcher = matchesId;
} else {
matcherParam = selector;
matcher = matches;
}
// Add to the list of listeners
listenerMap[eventType].push({
selector: selector,
handler: handler,
matcher: matcher,
matcherParam: matcherParam
});
return this;
};
/**
* Remove an event handler
* for elements that match
* the selector, forever
*
* @param {string} [eventType] Remove handlers for events matching this type, considering the other parameters
* @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed
* @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.off = function(eventType, selector, handler, useCapture) {
var i, listener, listenerMap, listenerList, singleEventType;
// Handler can be passed as
// the second or third argument
if (typeof selector === 'function') {
useCapture = handler;
handler = selector;
selector = null;
}
// If useCapture not set, remove
// all event listeners
if (useCapture === undefined) {
this.off(eventType, selector, handler, true);
this.off(eventType, selector, handler, false);
return this;
}
listenerMap = this.listenerMap[useCapture ? 1 : 0];
if (!eventType) {
for (singleEventType in listenerMap) {
if (listenerMap.hasOwnProperty(singleEventType)) {
this.off(singleEventType, selector, handler);
}
}
return this;
}
listenerList = listenerMap[eventType];
if (!listenerList || !listenerList.length) {
return this;
}
// Remove only parameter matches
// if specified
for (i = listenerList.length - 1; i >= 0; i--) {
listener = listenerList[i];
if ((!selector || selector === listener.selector) && (!handler || handler === listener.handler)) {
listenerList.splice(i, 1);
}
}
// All listeners removed
if (!listenerList.length) {
delete listenerMap[eventType];
// Remove the main handler
if (this.rootElement) {
this.rootElement.removeEventListener(eventType, this.handle, useCapture);
}
}
return this;
};
/**
* Handle an arbitrary event.
*
* @param {Event} event
*/
Delegate.prototype.handle = function(event) {
var i, l, type = event.type, root, phase, listener, returned, listenerList = [], target, /** @const */ EVENTIGNORE = 'ftLabsDelegateIgnore';
if (event[EVENTIGNORE] === true) {
return;
}
target = event.target;
// Hardcode value of Node.TEXT_NODE
// as not defined in IE8
if (target.nodeType === 3) {
target = target.parentNode;
}
root = this.rootElement;
phase = event.eventPhase || ( event.target !== event.currentTarget ? 3 : 2 );
switch (phase) {
case 1: //Event.CAPTURING_PHASE:
listenerList = this.listenerMap[1][type];
break;
case 2: //Event.AT_TARGET:
if (this.listenerMap[0] && this.listenerMap[0][type]) listenerList = listenerList.concat(this.listenerMap[0][type]);
if (this.listenerMap[1] && this.listenerMap[1][type]) listenerList = listenerList.concat(this.listenerMap[1][type]);
break;
case 3: //Event.BUBBLING_PHASE:
listenerList = this.listenerMap[0][type];
break;
}
// Need to continuously check
// that the specific list is
// still populated in case one
// of the callbacks actually
// causes the list to be destroyed.
l = listenerList.length;
while (target && l) {
for (i = 0; i < l; i++) {
listener = listenerList[i];
// Bail from this loop if
// the length changed and
// no more listeners are
// defined between i and l.
if (!listener) {
break;
}
// Check for match and fire
// the event if there's one
//
// TODO:MCG:20120117: Need a way
// to check if event#stopImmediatePropagation
// was called. If so, break both loops.
if (listener.matcher.call(target, listener.matcherParam, target)) {
returned = this.fire(event, target, listener);
}
// Stop propagation to subsequent
// callbacks if the callback returned
// false
if (returned === false) {
event[EVENTIGNORE] = true;
event.preventDefault();
return;
}
}
// TODO:MCG:20120117: Need a way to
// check if event#stopPropagation
// was called. If so, break looping
// through the DOM. Stop if the
// delegation root has been reached
if (target === root) {
break;
}
l = listenerList.length;
target = target.parentElement;
}
};
/**
* Fire a listener on a target.
*
* @param {Event} event
* @param {Node} target
* @param {Object} listener
* @returns {boolean}
*/
Delegate.prototype.fire = function(event, target, listener) {
return listener.handler.call(target, event, target);
};
/**
* Check whether an element
* matches a generic selector.
*
* @type function()
* @param {string} selector A CSS selector
*/
var matches = (function(el) {
if (!el) return;
var p = el.prototype;
return (p.matches || p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
}(Element));
/**
* Check whether an element
* matches a tag selector.
*
* Tags are NOT case-sensitive,
* except in XML (and XML-based
* languages such as XHTML).
*
* @param {string} tagName The tag name to test against
* @param {Element} element The element to test with
* @returns boolean
*/
function matchesTag(tagName, element) {
return tagName.toLowerCase() === element.tagName.toLowerCase();
}
/**
* Check whether an element
* matches the root.
*
* @param {?String} selector In this case this is always passed through as null and not used
* @param {Element} element The element to test with
* @returns boolean
*/
function matchesRoot(selector, element) {
/*jshint validthis:true*/
if (this.rootElement === window) return element === document;
return this.rootElement === element;
}
/**
* Check whether the ID of
* the element in 'this'
* matches the given ID.
*
* IDs are case-sensitive.
*
* @param {string} id The ID to test against
* @param {Element} element The element to test with
* @returns boolean
*/
function matchesId(id, element) {
return id === element.id;
}
/**
* Short hand for off()
* and root(), ie both
* with no parameters
*
* @return void
*/
Delegate.prototype.destroy = function() {
this.off();
this.root();
};
},{}],6:[function(require,module,exports){
/*jshint browser:true, node:true*/
'use strict';
/**
* @preserve Create and manage a DOM event delegator.
*
* @version 0.3.0
* @codingstandard ftlabs-jsv2
* @copyright The Financial Times Limited [All Rights Reserved]
* @license MIT License (see LICENSE.txt)
*/
var Delegate = require('./delegate');
module.exports = function(root) {
return new Delegate(root);
};
module.exports.Delegate = Delegate;
},{"./delegate":5}],7:[function(require,module,exports){
module.exports = get;
function get (context, path) {
if (path.indexOf('.') == -1 && path.indexOf('[') == -1) {
return context[path];
}
var crumbs = path.split(/\.|\[|\]/g);
var i = -1;
var len = crumbs.length;
var result;
while (++i < len) {
if (i == 0) result = context;
if (!crumbs[i]) continue;
if (result == undefined) break;
result = result[crumbs[i]];
}
return result;
}
},{}],8:[function(require,module,exports){
module.exports = require('./lib/supermodels');
},{"./lib/supermodels":18}],9:[function(require,module,exports){
'use strict'
var util = require('./util')
var createWrapperFactory = require('./factory')
function resolve (from) {
var isCtor = util.isConstructor(from)
var isSupermodelCtor = util.isSupermodelConstructor(from)
var isArray = util.isArray(from)
if (isCtor || isSupermodelCtor || isArray) {
return {
__type: from
}
}
var isValue = !util.isObject(from)
if (isValue) {
return {
__value: from
}
}
return from
}
function createDef (from) {
from = resolve(from)
var __VALIDATORS = '__validators'
var __VALUE = '__value'
var __TYPE = '__type'
var __DISPLAYNAME = '__displayName'
var __GET = '__get'
var __SET = '__set'
var __ENUMERABLE = '__enumerable'
var __CONFIGURABLE = '__configurable'
var __WRITABLE = '__writable'
var __SPECIAL_PROPS = [
__VALIDATORS, __VALUE, __TYPE, __DISPLAYNAME,
__GET, __SET, __ENUMERABLE, __CONFIGURABLE, __WRITABLE
]
var def = {
from: from,
type: from[__TYPE],
value: from[__VALUE],
validators: from[__VALIDATORS] || [],
enumerable: from[__ENUMERABLE] !== false,
configurable: !!from[__CONFIGURABLE],
writable: from[__WRITABLE] !== false,
displayName: from[__DISPLAYNAME],
getter: from[__GET],
setter: from[__SET]
}
var type = def.type
// Simple 'Constructor' Type
if (util.isSimpleConstructor(type)) {
def.isSimple = true
def.cast = function (value) {
return util.cast(value, type)
}
} else if (util.isSupermodelConstructor(type)) {
def.isReference = true
} else if (def.value) {
// If a value is present, use
// that and short-circuit the rest
def.isSimple = true
} else {
// Otherwise look for other non-special
// keys and also any item definition
// in the case of Arrays
var keys = Object.keys(from)
var childKeys = keys.filter(function (item) {
return __SPECIAL_PROPS.indexOf(item) === -1
})
if (childKeys.length) {
var defs = {}
var proto
childKeys.forEach(function (key) {
var descriptor = Object.getOwnPropertyDescriptor(from, key)
var value
if (descriptor.get || descriptor.set) {
value = {
__get: descriptor.get,
__set: descriptor.set
}
} else {
value = from[key]
}
if (!util.isConstructor(value) && !util.isSupermodelConstructor(value) && util.isFunction(value)) {
if (!proto) {
proto = {}
}
proto[key] = value
} else {
defs[key] = createDef(value)
}
})
def.defs = defs
def.proto = proto
}
// Check for Array
if (type === Array || util.isArray(type)) {
def.isArray = true
if (type.length > 0) {
def.def = createDef(type[0])
}
} else if (childKeys.length === 0) {
def.isSimple = true
}
}
def.create = createWrapperFactory(def)
return def
}
module.exports = createDef
},{"./factory":13,"./util":19}],10:[function(require,module,exports){
'use strict'
module.exports = function (callback) {
var arr = []
/**
* Proxied array mutators methods
*
* @param {Object} obj
* @return {Object}
* @api private
*/
var pop = function () {
var result = Array.prototype.pop.apply(arr)
callback('pop', arr, {
value: result
})
return result
}
var push = function () {
var result = Array.prototype.push.apply(arr, arguments)
callback('push', arr, {
value: result
})
return result
}
var shift = function () {
var result = Array.prototype.shift.apply(arr)
callback('shift', arr, {
value: result
})
return result
}
var sort = function () {
var result = Array.prototype.sort.apply(arr, arguments)
callback('sort', arr, {
value: result
})
return result
}
var unshift = function () {
var result = Array.prototype.unshift.apply(arr, arguments)
callback('unshift', arr, {
value: result
})
return result
}
var reverse = function () {
var result = Array.prototype.reverse.apply(arr)
callback('reverse', arr, {
value: result
})
return result
}
var splice = function () {
if (!arguments.length) {
return
}
var result = Array.prototype.splice.apply(arr, arguments)
callback('splice', arr, {
value: result,
removed: result,
added: Array.prototype.slice.call(arguments, 2)
})
return result
}
/**
* Proxy all Array.prototype mutator methods on this array instance
*/
arr.pop = arr.pop && pop
arr.push = arr.push && push
arr.shift = arr.shift && shift
arr.unshift = arr.unshift && unshift
arr.sort = arr.sort && sort
arr.reverse = arr.reverse && reverse
arr.splice = arr.splice && splice
/**
* Special update function since we can't detect
* assignment by index e.g. arr[0] = 'something'
*/
arr.update = function (index, value) {
var oldValue = arr[index]
var newValue = arr[index] = value
callback('update', arr, {
index: index,
value: newValue,
oldValue: oldValue
})
return newValue
}
return arr
}
},{}],11:[function(require,module,exports){
'use strict'
module.exports = function EmitterEvent (name, path, target, detail) {
this.name = name
this.path = path
this.target = target
if (detail) {
this.detail = detail
}
}
},{}],12:[function(require,module,exports){
'use strict'
/**
* Expose `Emitter`.
*/
module.exports = Emitter
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter (obj) {
var ctx = obj || this
if (obj) {
ctx = mixin(obj)
return ctx
}
}
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
function mixin (obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key]
}
return obj
}
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.on = Emitter.prototype.addEventListener = function (event, fn) {
(this.__callbacks[event] = this.__callbacks[event] || [])
.push(fn)
return this
}
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.once = function (event, fn) {
function on () {
this.off(event, on)
fn.apply(this, arguments)
}
on.fn = fn
this.on(event, on)
return this
}
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.off = Emitter.prototype.removeEventListener = Emitter.prototype.removeAllListeners = function (event, fn) {
// all
if (arguments.length === 0) {
this.__callbacks = {}
return this
}
// specific event
var callbacks = this.__callbacks[event]
if (!callbacks) {
return this
}
// remove all handlers
if (arguments.length === 1) {
delete this.__callbacks[event]
return this
}
// remove specific handler
var cb
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i]
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1)
break
}
}
return this
}
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
Emitter.prototype.emit = function (event) {
var args = [].slice.call(arguments, 1)
var callbacks = this.__callbacks[event]
if (callbacks) {
callbacks = callbacks.slice(0)
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args)
}
}
return this
}
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
Emitter.prototype.listeners = function (event) {
return this.__callbacks[event] || []
}
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
Emitter.prototype.hasListeners = function (event) {
return !!this.listeners(event).length
}
},{}],13:[function(require,module,exports){
'use strict'
var util = require('./util')
var createModelPrototype = require('./proto')
var Wrapper = require('./wrapper')
function createModelDescriptors (def, parent) {
var __ = {}
var desc = {
__: {
value: __
},
__def: {
value: def
},
__parent: {
value: parent,
writable: true
},
__callbacks: {
value: {},
writable: true
}
}
return desc
}
function defineProperties (model) {
var defs = model.__def.defs
for (var key in defs) {
defineProperty(model, key, defs[key])
}
}
function defineProperty (model, key, def) {
var desc = {
get: function () {
return this.__get(key)
},
enumerable: def.enumerable,
configurable: def.configurable
}
if (def.writable) {
desc.set = function (value) {
this.__setNotifyChange(key, value)
}
}
Object.defineProperty(model, key, desc)
// Silently initialize the property wrapper
model.__[key] = def.create(model)
}
function createWrapperFactory (def) {
var wrapper, defaultValue, assert
if (def.isSimple) {
wrapper = new Wrapper(def.value, def.writable, def.validators, def.getter, def.setter, def.cast, null)
} else if (def.isReference) {
// Hold a reference to the
// refererenced types' definition
var refDef = def.type.def
if (refDef.isSimple) {
// If the referenced type is itself simple,
// we can set just return a wrapper and
// the property will get initialized.
wrapper = new Wrapper(refDef.value, refDef.writable, refDef.validators, def.getter, def.setter, refDef.cast, null)
} else {
// If we're not dealing with a simple reference model
// we need to define an assertion that the instance
// being set is of the correct type. We do this be
// comparing the defs.
assert = function (value) {
// compare the defintions of the value instance
// being passed and the def property attached
// to the type SupermodelConstructor. Allow the
// value to be undefined or null also.
var isCorrectType = false
if (util.isNullOrUndefined(value)) {
isCorrectType = true
} else {
isCorrectType = refDef === value.__def
}
if (!isCorrectType) {
throw new Error('Value should be an instance of the referenced model, null or undefined')
}
}
wrapper = new Wrapper(def.value, def.writable, def.validators, def.getter, def.setter, null, assert)
}
} else if (def.isArray) {
defaultValue = function (parent) {
// for Arrays, we create a new Array and each
// time, mix the model properties into it
var model = createModelPrototype(def)
Object.defineProperties(model, createModelDescriptors(def, parent))
defineProperties(model)
return model
}
assert = function (value) {
// todo: further array type validation
if (!util.isArray(value)) {
throw new Error('Value should be an array')
}
}
wrapper = new Wrapper(defaultValue, def.writable, def.validators, def.getter, def.setter, null, assert)
} else {
// for Objects, we can create and reuse
// a prototype object. We then need to only
// define the defs and the 'instance' properties
// e.g. __, parent etc.
var proto = createModelPrototype(def)
defaultValue = function (parent) {
var model = Object.create(proto, createModelDescriptors(def, parent))
defineProperties(model)
return model
}
assert = function (value) {
if (!proto.isPrototypeOf(value)) {
throw new Error('Invalid prototype')
}
}
wrapper = new Wrapper(defaultValue, def.writable, def.validators, def.getter, def.setter, null, assert)
}
var factory = function (parent) {
var wrap = Object.create(wrapper)
// if (!wrap.isInitialized) {
wrap.initialize(parent)
// }
return wrap
}
// expose the wrapper, this is used
// for validating array items later
factory.wrapper = wrapper
return factory
}
module.exports = createWrapperFactory
},{"./proto":16,"./util":19,"./wrapper":21}],14:[function(require,module,exports){
'use strict'
function merge (model, obj) {
var isArray = model.__def.isArray
var defs = model.__def.defs
var defKeys, def, key, i, isSimple,
isSimpleReference, isInitializedReference
if (defs) {
defKeys = Object.keys(defs)
for (i = 0; i < defKeys.length; i++) {
key = defKeys[i]
if (obj.hasOwnProperty(key)) {
def = defs[key]
isSimple = def.isSimple
isSimpleReference = def.isReference && def.type.def.isSimple
isInitializedReference = def.isReference && obj[key] && obj[key].__supermodel
if (isSimple || isSimpleReference || isInitializedReference) {
model[key] = obj[key]
} else if (obj[key]) {
if (def.isReference) {
model[key] = def.type()
}
merge(model[key], obj[key])
}
}
}
}
if (isArray && Array.isArray(obj)) {
for (i = 0; i < obj.length; i++) {
var item = model.create()
model.push(item && item.__supermodel ? merge(item, obj[i]) : obj[i])
}
}
return model
}
module.exports = merge
},{}],15:[function(require,module,exports){
'use strict'
var EmitterEvent = require('./emitter-event')
var ValidationError = require('./validation-error')
var Wrapper = require('./wrapper')
var merge = require('./merge')
var descriptors = {
__supermodel: {
value: true
},
__keys: {
get: function () {
var keys = Object.keys(this)
if (Array.isArray(this)) {
var omit = [
'addEventListener', 'on', 'once', 'removeEventListener', 'removeAllListeners',
'removeListener', 'off', 'emit', 'listeners', 'hasListeners', 'pop', 'push',
'reverse', 'shift', 'sort', 'splice', 'update', 'unshift', 'create', '__merge',
'__setNotifyChange', '__notifyChange', '__set', '__get', '__chain', '__relativePath'
]
keys = keys.filter(function (item) {
return omit.indexOf(item) < 0
})
}
return keys
}
},
__name: {
get: function () {
if (this.__isRoot) {
return ''
}
// Work out the 'name' of the model
// Look up to the parent and loop through it's keys,
// Any value or array found to contain the value of this (this model)
// then we return the key and index in the case we found the model in an array.
var parentKeys = this.__parent.__keys
var parentKey, parentValue
for (var i = 0; i < parentKeys.length; i++) {
parentKey = parentKeys[i]
parentValue = this.__parent[parentKey]
if (parentValue === this) {
return parentKey
}
}
}
},
__path: {
get: function () {
if (this.__hasAncestors && !this.__parent.__isRoot) {
return this.__parent.__path + '.' + this.__name
} else {
return this.__name
}
}
},
__isRoot: {
get: function () {
return !this.__hasAncestors
}
},
__children: {
get: function () {
var children = []
var keys = this.__keys
var key, value
for (var i = 0; i < keys.length; i++) {
key = keys[i]
value = this[key]
if (value && value.__supermodel) {
children.push(value)
}
}
return children
}
},
__ancestors: {
get: function () {
var ancestors = []
var r = this
while (r.__parent) {
ancestors.push(r.__parent)
r = r.__parent
}
return ancestors
}
},
__descendants: {
get: function () {
var descendants = []
function checkAndAddDescendantIfModel (obj) {
var keys = obj.__keys
var key, value
for (var i = 0; i < keys.length; i++) {
key = keys[i]
value = obj[key]
if (value && value.__supermodel) {
descendants.push(value)
checkAndAddDescendantIfModel(value)
}
}
}
checkAndAddDescendantIfModel(this)
return descendants
}
},
__hasAncestors: {
get: function () {
return !!this.__ancestors.length
}
},
__hasDescendants: {
get: function () {
return !!this.__descendants.length
}
},
errors: {
get: function () {
var errors = []
var def = this.__def
var validator, error, i, j
// Run own validators
var own = def.validators.slice(0)
for (i = 0; i < own.length; i++) {
validator = own[i]
error = validator.call(this, this)
if (error) {
errors.push(new ValidationError(this, error, validator))
}
}
// Run through keys and evaluate validators
var keys = this.__keys
var value, key, itemDef
for (i = 0; i < keys.length; i++) {
key = keys[i]
// If we are an Array with an item definition
// then we have to look into the Array for our value
// and also get hold of the wrapper. We only need to
// do this if the key is not a property of the array.
// We check the defs to work this out (i.e. 0, 1, 2).
// todo: This could be better to check !NaN on the key?
if (def.isArray && def.def && (!def.defs || !(key in def.defs))) {
// If we are an Array with a simple item definition
// or a reference to a simple type definition
// substitute the value with the wrapper we get from the
// create factory function. Otherwise set the value to
// the real value of the property.
itemDef = def.def
if (itemDef.isSimple) {
value = itemDef.create.wrapper
value.setValue(this[key])
} else if (itemDef.isReference && itemDef.type.def.isSimple) {
value = itemDef.type.def.create.wrapper
value.setValue(this[key])
} else {
value = this[key]
}
} else {
// Set the value to the wrapped value of the property
value = this.__[key]
}
if (value) {
if (value.__supermodel) {
Array.prototype.push.apply(errors, value.errors)
} else if (value instanceof Wrapper) {
var wrapperValue = value.getValue(this)
if (wrapperValue && wrapperValue.__supermodel) {
Array.prototype.push.apply(errors, wrapperValue.errors)
} else {
var simple = value.validators
for (j = 0; j < simple.length; j++) {
validator = simple[j]
error = validator.call(this, wrapperValue, key)
if (error) {
errors.push(new ValidationError(this, error, validator, key))
}
}
}
}
}
}
return errors
}
}
}
var proto = {
__get: function (key) {
return this.__[key].getValue(this)
},
__set: function (key, value) {
this.__[key].setValue(value, this)
},
__relativePath: function (to, key) {
var relativePath = this.__path ? to.substr(this.__path.length + 1) : to
if (relativePath) {
return key ? relativePath + '.' + key : relativePath
}
return key
},
__chain: function (fn) {
return [this].concat(this.__ancestors).forEach(fn)
},
__merge: function (data) {
return merge(this, data)
},
__notifyChange: function (key, newValue, oldValue) {
var target = this
var targetPath = this.__path
var eventName = 'set'
var data = {
oldValue: oldValue,
newValue: newValue
}
this.emit(eventName, new EmitterEvent(eventName, key, target, data))
this.emit('change', new EmitterEvent(eventName, key, target, data))
this.emit('change:' + key, new EmitterEvent(eventName, key, target, data))
this.__ancestors.forEach(function (item) {
var path = item.__relativePath(targetPath, key)
item.emit('change', new EmitterEvent(eventName, path, target, data))
})
},
__setNotifyChange: function (key, value) {
var oldValue = this.__get(key)
this.__set(key, value)
var newValue = this.__get(key)
this.__notifyChange(key, newValue, oldValue)
}
}
module.exports = {
proto: proto,
descriptors: descriptors
}
},{"./emitter-event":11,"./merge":14,"./validation-error":20,"./wrapper":21}],16:[function(require,module,exports){
'use strict'
var emitter = require('./emitter-object')
var emitterArray = require('./emitter-array')
var EmitterEvent = require('./emitter-event')
var extend = require('./util').extend
var model = require('./model')
var modelProto = model.proto
var modelDescriptors = model.descriptors
var modelPrototype = Object.create(modelProto, modelDescriptors)
var objectPrototype = (function () {
var p = Object.create(modelPrototype)
emitter(p)
return p
})()
function createArrayPrototype () {
var p = emitterArray(function (eventName, arr, e) {
if (eventName === 'update') {
/**
* Forward the special array update
* events as standard __notifyChange events
*/
arr.__notifyChange(e.index, e.value, e.oldValue)
} else {
/**
* All other events e.g. push, splice are relayed
*/
var target = arr
var path = arr.__path
var data = e
var key = e.index
arr.emit(eventName, new EmitterEvent(eventName, '', target, data))
arr.emit('change', new EmitterEvent(eventName, '', target, data))
arr.__ancestors.forEach(function (item) {
var name = item.__relativePath(path, key)
item.emit('change', new EmitterEvent(eventName, name, target, data))
})
}
})
Object.defineProperties(p, modelDescriptors)
emitter(p)
extend(p, modelProto)
return p
}
function createObjectModelPrototype (proto) {
var p = Object.create(objectPrototype)
if (proto) {
extend(p, proto)
}
return p
}
function createArrayModelPrototype (proto, itemDef) {
// We do not to attempt to subclass Array,
// instead create a new instance each time
// and mixin the proto object
var p = createArrayPrototype()
if (proto) {
extend(p, proto)
}
if (itemDef) {
// We have a definition for the items
// that belong in this array.
// Use the `wrapper` prototype property as a
// virtual Wrapper object we can use
// validate all the items in the array.
var arrItemWrapper = itemDef.create.wrapper
// Validate new models by overriding the emitter array
// mutators that can cause new items to enter the array.
overrideArrayAddingMutators(p, arrItemWrapper)
// Provide a convenient model factory
// for creating array item instances
p.create = function () {
return itemDef.isReference ? itemDef.type() : itemDef.create().getValue(this)
}
}
return p
}
function overrideArrayAddingMutators (arr, itemWrapper) {
function getArrayArgs (items) {
var args = []
for (var i = 0; i < items.length; i++) {
itemWrapper.setValue(items[i], arr)
args.push(itemWrapper.getValue(arr))
}
return args
}
var push = arr.push
var unshift = arr.unshift
var splice = arr.splice
var update = arr.update
if (push) {
arr.push = function () {
var args = getArrayArgs(arguments)
return push.apply(arr, args)
}
}
if (unshift) {
arr.unshift = function () {
var args = getArrayArgs(arguments)
return unshift.apply(arr, args)
}
}
if (splice) {
arr.splice = function () {
var args = getArrayArgs(Array.prototype.slice.call(arguments, 2))
args.unshift(arguments[1])
args.unshift(arguments[0])
return splice.apply(arr, args)
}
}
if (update) {
arr.update = function () {
var args = getArrayArgs([arguments[1]])
args.unshift(arguments[0])
return update.apply(arr, args)
}
}
}
function createModelPrototype (def) {
return def.isArray ? createArrayModelPrototype(def.proto, def.def) : createObjectModelPrototype(def.proto)
}
module.exports = createModelPrototype
},{"./emitter-array":10,"./emitter-event":11,"./emitter-object":12,"./model":15,"./util":19}],17:[function(require,module,exports){
'use strict'
module.exports = {}
},{}],18:[function(require,module,exports){
'use strict'
// var merge = require('./merge')
var createDef = require('./def')
var Supermodel = require('./supermodel')
function supermodels (schema, initializer) {
var def = createDef(schema)
function SupermodelConstructor (data) {
var model = def.isSimple ? def.create() : def.create().getValue({})
// Call any initializer
if (initializer) {
initializer.apply(model, arguments)
} else if (data) {
// if there's no initializer
// but we have been passed some
// data, merge it into the model.
model.__merge(data)
}
return model
}
Object.defineProperty(SupermodelConstructor, 'def', {
value: def // this is used to validate referenced SupermodelConstructors
})
SupermodelConstructor.prototype = Supermodel // this shared object is used, as a prototype, to identify SupermodelConstructors
SupermodelConstructor.constructor = SupermodelConstructor
return SupermodelConstructor
}
module.exports = supermodels
},{"./def":9,"./supermodel":17}],19:[function(require,module,exports){
'use strict'
var Supermodel = require('./supermodel')
function extend (origin, add) {
// Don