starplate
Version:
View engine built on incremental-dom
1,984 lines (1,676 loc) • 455 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.starplate = f()}})(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);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){
'use strict';
/**
* Module dependencies.
*/
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { 'default': obj };
}
var _template = require('./template');
var _template2 = _interopRequireDefault(_template);
var _parser = require('./parser');
var _parser2 = _interopRequireDefault(_parser);
var _view = require('./view');
var _view2 = _interopRequireDefault(_view);
/**
* Module exports.
*/
exports.Template = _template2['default'];
exports.Parser = _parser2['default'];
exports.View = _view2['default'];
},{"./parser":2,"./template":3,"./view":4}],2:[function(require,module,exports){
'use strict';
/**
* Module dependencies.
*/
Object.defineProperty(exports, '__esModule', {
value: true
});
var _createClass = (function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ('value' in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
}
}return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
};
})();
var _get = function get(_x, _x2, _x3) {
var _again = true;_function: while (_again) {
var object = _x,
property = _x2,
receiver = _x3;_again = false;if (object === null) object = Function.prototype;var desc = Object.getOwnPropertyDescriptor(object, property);if (desc === undefined) {
var parent = Object.getPrototypeOf(object);if (parent === null) {
return undefined;
} else {
_x = parent;_x2 = property;_x3 = receiver;_again = true;desc = parent = undefined;continue _function;
}
} else if ('value' in desc) {
return desc.value;
} else {
var getter = desc.get;if (getter === undefined) {
return undefined;
}return getter.call(receiver);
}
}
};
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { 'default': obj };
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
var _parse5 = require('parse5');
var _parse52 = _interopRequireDefault(_parse5);
var _incrementalDom = require('incremental-dom');
/**
* Generates a random unique hex ID string.
*
* @private
* @function
* @name uid
* @return {String}
*/
var uid = function uid(_) {
return Math.abs(Math.random() * Date.now() | 1).toString('16');
};
/**
* Ensures a function.
*
* @private
* @function
* @name ensureFunction
* @param {Mixed} fn
* @return {Function}
*/
var ensureFunction = function ensureFunction(fn) {
return 'function' == typeof fn ? fn : function () {
return void 0;
};
};
/**
* Parser class.
*
* @public
* @class Parser
* @extends parse5.Parser
*/
// Parser shared instance
var instance_ = null;
var Parser = (function (_parse5$Parser) {
_inherits(Parser, _parse5$Parser);
_createClass(Parser, null, [{
key: 'sharedInstance',
/**
* Shared parser instance
*
* @public
* @static
* @method
* @name sharedInstance
* @return {Parser}
*/
value: function sharedInstance() {
instance_ = instance_ || new Parser();
return instance_;
}
/**
* Parser constructor.
*
* @public
* @constructor
*/
}]);
function Parser() {
_classCallCheck(this, Parser);
_get(Object.getPrototypeOf(Parser.prototype), 'constructor', this).call(this, _parse52['default'].TreeAdapters.htmlparser2);
/**
* Known patches for this parser state.
*
* @public
* @type {Map}
* @name patches
*/
this.patches = new Map();
}
/**
* Creates a patch function used for updating
* a given DOM Element from the provided source
* HTML or DOM Element.
*
* @public
* @method
* @name createPatch
* @param {String|Element} source
* @return {Function} (domElement, [done]) => {Undefined}
*/
_createClass(Parser, [{
key: 'createPatch',
value: function createPatch(source) {
var html = source;
// get cached patch if diff doesn't exist
if (!this.hasPatch(source)) {
return this.getPatch(source);
}
// consume source HTML if an element is given
if (source instanceof HTMLElement) {
html = source.innerHTML;
}
html = String(html).replace(/\n/g, ' ').replace(/\r/g, ' ');
var root = this.parseFragment(html);
var nodes = root.children;
var stack = [];
/**
* Creates and pushes an instruction
* to the render stack.
*
* @private
* @function
* @name createInstruction
* @param {Function} fn
*/
var createInstruction = function createInstruction(fn) {
return stack.push(fn);
};
/**
* Call each routine in stack.
*
* @private
* @function
* @name render
*/
var render = function render(_) {
return stack.forEach(function (routine) {
return routine();
});
};
/**
* Patch routine for a given DOM Element.
*
* @public
* @function
* @param {Element} domElement
* @param {Function} [done]
*/
var partial = function partial(domElement, done) {
done = ensureFunction(done);
(0, _incrementalDom.patch)(domElement, function (_) {
stack.forEach(function (routine) {
return routine();
});
done();
});
};
/**
* Traverse node recursively appending
* instructions to stack.
*
* @private
* @function
* @name traverse
* @param {Object} node
*/
function traverse(node) {
var kv = [];
var id = node.attribs ? node.attribs.id : uid();
var attrs = node.attribs;
var parent = node.parent;
var hasChildren = Boolean(node.children ? node.children.length : 0);
if (attrs && Object.keys(attrs).length) for (var key in attrs) {
if (attrs[key]) kv.push(key, attrs[key]);
}if ('tag' == node.type) {
// begin node
createInstruction(function (_) {
return _incrementalDom.elementOpen.apply(undefined, [node.name, id, null].concat(kv));
});
// define child nodes
if (hasChildren) node.children.forEach(traverse);
// close node
createInstruction(function (_) {
return (0, _incrementalDom.elementClose)(node.name);
});
} else if ('text' == node.type && node.data) {
// handle text nodes
createInstruction(function (_) {
return (0, _incrementalDom.text)(node.data);
});
} else if ('script' == node.type) {
// skip script
} else {
// @TODO(werle) - what else ?
throw new TypeError('Unhandled node type ' + node.type + '.');
}
};
// Walk tree and generate
// incremental DOM routines
nodes.forEach(traverse);
// set patch
this.patches.set(source, partial);
// provide partial patch function
return partial;
}
/**
* Predicate to determine if source given
* is an already defined patch.
*
* @public
* @method
* @name hasPatch
* @param {Mixed} source
* @return {Boolean}
*/
}, {
key: 'hasPatch',
value: function hasPatch(source) {
return false == this.patches.has(source);
}
/**
* Returns patch by source.
*
* @public
* @method
* @name getPatch
* @param {Mixed} source
* @return {Function}
*/
}, {
key: 'getPatch',
value: function getPatch(source) {
return this.patches.get(source) || null;
}
}]);
return Parser;
})(_parse52['default'].Parser);
exports['default'] = Parser;
module.exports = exports['default'];
},{"incremental-dom":7,"parse5":8}],3:[function(require,module,exports){
'use strict';
/**
* Module dependencies.
*/
Object.defineProperty(exports, '__esModule', {
value: true
});
var _createClass = (function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ('value' in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
}
}return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
};
})();
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
var _view = require('./view');
/**
* Ensures an object.
*
* @private
* @function
* @name ensureObject
* @param {Mixed} o
* @return {Object}
*/
var ensureObject = function ensureObject(o) {
return null != o && 'object' == typeof o ? o : {};
};
/**
* Recursively makes an object safe for partial
* usage.
*
* @private
* @function
* @name makeSafeObject
* @param {Mixed} o
* @return {Mixed}
*/
function makeSafeObject(o) {
var out = String();
if ('function' == typeof o) {
return o;
}
if (null == o || 'object' != typeof o) {
if ('string' == typeof o) {
try {
return JSON.stringify(JSON.parse(o));
} catch (e) {}
}
return JSON.stringify(o);
}
if ('object' == typeof o) {
for (var k in o) {
o[k] = makeSafeObject(o[k]);
}if (Array.isArray(o)) {
out += '[';
for (var k in o) {
out += o[k] + ', ';
}out += ']';
} else {
out += '{';
for (var k in o) {
out += k + ': ' + o[k] + ', ';
}out += '}';
}
}
return out;
}
/**
* Template class.
*
* @public
* @class Template
*/
var Template = (function () {
_createClass(Template, null, [{
key: 'createPartial',
/**
* Creates a function that accepts an optional
* object creating a variable scope for the
* template string. You may pass a string or
* function. If a function is passed it is
* called when the partial is created. All
* data is propagated to functions passed to
* this function.
*
* @public
* @static
* @method
* @name createPartial
* @param {String|Function}
* @return {Function} (data) => {String}
*/
value: function createPartial(string) {
var _this = this;
if ('string' == typeof string) string = string.replace(RegExp('`', 'g', '\\`'));
/**
* Partial template function that accepts
* an optional variable scope object.
*
* @public
* @function
* @param {Object} [data = {}]
* @return {String}
*/
return function (data, scope) {
data = ensureObject(data);
scope = scope || _this;
var wrap = string;
var header = Object.keys(data).filter(function (key) {
return false == _view.helpers.has(key);
}).map(function (key) {
var value = makeSafeObject(data[key]);
return key + ' = ' + value;
});
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = _view.helpers.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var kv = _step.value;
header.push(kv[0] + ' = ' + makeSafeObject(kv[1]));
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator['return']) {
_iterator['return']();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
header = header.length ? 'var ' + header.join(', ') + ';' : '';
// allow use of #{} inside of ES6 template strings
if ('string' == typeof string) string = string.replace(/\#\{/g, '${');
if ('function' != typeof wrap) wrap = new Function('data', '\'use strict\'; ' + header + ' return `' + string + '`');
var src = '\'use strict\'; return wrap.call(this, data);';
var fn = new Function('data', 'wrap', src);
return String(fn.call(scope, data, wrap) || '');
};
}
/**
* Template class constructor.
*
* @public
* @constructor
* @param {String|Function} source
*/
}]);
function Template(source) {
_classCallCheck(this, Template);
/**
* The template source.
*
* @public
* @type {Function|String}
* @name source
*/
this.source = null;
/**
* A partial function used to
* render a template.
*
* @public
* @method
* @name render
* @param {Object} [data = {}]
*/
this.render = null;
// intial definition
this.define(source);
}
/**
* Defines the template source.
*
* @public
* @method
* @name define
* @param {String|Function} source
*/
_createClass(Template, [{
key: 'define',
value: function define(source) {
this.source = source;
this.render = Template.createPartial(source);
return this;
}
/**
* Implements toString.
*
* @public
* @method
* @name toString
* @return {String}
*/
}, {
key: 'toString',
value: function toString() {
return String(this.source || '');
}
/**
* Implements valueOf.
*
* @public
* @method
* @name valueOf
* @return {Element}
*/
}, {
key: 'valueOf',
value: function valueOf() {
return this.source;
}
}]);
return Template;
})();
exports['default'] = Template;
module.exports = exports['default'];
},{"./view":4}],4:[function(require,module,exports){
"use strict";
/**
* Module dependencies.
*/
Object.defineProperty(exports, '__esModule', {
value: true
});
var _createClass = (function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ('value' in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
}
}return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
};
})();
var _get = function get(_x3, _x4, _x5) {
var _again = true;_function: while (_again) {
var object = _x3,
property = _x4,
receiver = _x5;_again = false;if (object === null) object = Function.prototype;var desc = Object.getOwnPropertyDescriptor(object, property);if (desc === undefined) {
var parent = Object.getPrototypeOf(object);if (parent === null) {
return undefined;
} else {
_x3 = parent;_x4 = property;_x5 = receiver;_again = true;desc = parent = undefined;continue _function;
}
} else if ('value' in desc) {
return desc.value;
} else {
var getter = desc.get;if (getter === undefined) {
return undefined;
}return getter.call(receiver);
}
}
};
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { 'default': obj };
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
}subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
var _events = require('events');
var _template = require('./template');
var _template2 = _interopRequireDefault(_template);
var _parser = require('./parser');
var _parser2 = _interopRequireDefault(_parser);
/**
* Grab first element of an array like object
* if possible, otherwise use argument.
*
* @private
* @function
* @name first
* @param {Mixed} a
* @return {Mixed}
*/
var first = function first(a) {
return 'string' == typeof a ? new Text(a) : a && a.length && a[0] ? a[0] : a;
};
/**
* Creates a DOM from an HTML string.
*
* @private
* @function
* @name dom
* @param {String} html
* @return {Element|NodeList}
*/
var dom = function dom(html) {
var body = document.createElement('body');
var tmp = document.createDocumentFragment();
var nodes = null;
try {
body.innerHTML = html;
tmp.appendChild(body);
nodes = body.children;
} catch (e) {}
return (nodes && nodes.length > 1 ? nodes : nodes[0]) || html;
};
/**
* Deep merge objects
*
* @private
* @function
* @name merge
* @param {Object} a
* @param {Object} b
* @return {Object}
*/
var merge = function merge(a, b) {
for (var k in b) {
if ('object' == typeof b[k] && 'object' == typeof a[k]) merge(a[k], b[k]);else if ('object' == typeof b[k]) a[k] = merge(Array.isArray(b[k]) ? [] : {}, b[k]);else a[k] = b[k];
}
return a;
};
/**
* Clone object
*
* @private
* @function
* @name clone
* @param {Object} a
* @return {Object}
*/
var clone = function clone(a) {
return merge(Array.isArray(a) ? [] : {}, a);
};
/**
* Known view helpers defined with View.helper().
*
* @public
* @const
* @type {Map}
* @name helpers
*/
var helpers = new Map();
exports.helpers = helpers;
/**
* View class.
*
* @public
* @class View
* @extends EventEmitter
*/
var View = (function (_EventEmitter) {
_inherits(View, _EventEmitter);
_createClass(View, null, [{
key: 'helper',
/**
* Gets or sets a helper by name.
*
* @public
* @static
* @method
* @name helper
* @param {String} name
* @param {Function} [definition]
* @return {View|Function}
*/
value: function helper(name, definition) {
if (name && definition) {
if ('function' != typeof definition) {
throw new TypeError("Expecting definition to be a function.");
}
if ('string' != typeof name) {
throw new TypeError("Expecting name to be a string.");
}
helpers.set(name, definition);
return View;
} else if (name) {
return helpers.get(name) || null;
}
throw new TypeError("Expecting at least 1 argument.");
}
/**
* View class constructor.
*
* @public
* @constructor
* @param {Template} template
* @param {Object} model
*/
}]);
function View(template) {
var model = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
_classCallCheck(this, View);
_get(Object.getPrototypeOf(View.prototype), 'constructor', this).call(this);
// ensure template
if (false == template instanceof _template2['default']) {
template = new _template2['default'](template);
}
/**
* View data model.
*
* @public
* @type {Object}
* @name model
*/
this.model = model || {};
/**
* The template associated with this view.
*
* @public
* @type {Template}
* @name template
*/
this.template = template;
/**
* The DOM Element associated with this view.
*
* @public
* @type {Element}
* @name domElement
*/
this.domElement = first(dom(this.template.render(this.model)));
}
/**
* Renders view to target DOM element.
*
* @public
* @method
* @name render
* @param {Element} parentDomElement
* @return {View}
*/
_createClass(View, [{
key: 'render',
value: function render() {
var parentDomElement = arguments.length <= 0 || arguments[0] === undefined ? document.body : arguments[0];
var domElement = this.domElement;
// ensure DOM element
if (false == parentDomElement instanceof Element) {
throw new TypeError("Expecting a DOM Element.");
}
// only append if parent does not contain element
if (false == parentDomElement.contains(domElement)) {
parentDomElement.appendChild(domElement);
}
return this;
}
/**
* Updates DOM element with optional data
*
* @public
* @method
* @name update
* @param {Object} data
* @return {View}
*/
}, {
key: 'update',
value: function update(data) {
this.model = merge(this.model || {}, data || {});
this.patch(dom(this.template.render(clone(this.model))));
return this;
}
/**
* Patches view DOM tree with source string
* or a given DOM Element.
*
* @public
* @method
* @name patch
* @param {String|Element} source
* @return {View}
*/
}, {
key: 'patch',
value: function patch(source) {
var domElement = this.domElement;
var parser = _parser2['default'].sharedInstance();
var patch = parser.createPatch(source);
patch(domElement);
return this;
}
/**
* Destroys view and removes DOM element.
*
* @public
* @method
* @name destroy
* @return {View}
*/
}, {
key: 'destroy',
value: function destroy() {
var domElement = this.domElement;
var parentElement = domElement && domElement.parentElement;
if (parentElement && domElement) {
if (parentElement.contains(domElement)) {
parentElement.removeChild(domElement);
}
}
return this;
}
/**
* Implements toString.
*
* @public
* @method
* @name toString
* @return {String}
*/
}, {
key: 'toString',
value: function toString() {
return String(this.domElement ? this.domElement.outerHTML || '' : '');
}
/**
* Implements valueOf.
*
* @public
* @method
* @name valueOf
* @return {Element}
*/
}, {
key: 'valueOf',
value: function valueOf() {
return this.domElement;
}
}]);
return View;
})(_events.EventEmitter);
exports['default'] = View;
},{"./parser":2,"./template":3,"events":5}],5:[function(require,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
function EventEmitter() {
this._events = this._events || {};
this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
if (!isNumber(n) || n < 0 || isNaN(n))
throw TypeError('n must be a positive number');
this._maxListeners = n;
return this;
};
EventEmitter.prototype.emit = function(type) {
var er, handler, len, args, i, listeners;
if (!this._events)
this._events = {};
// If there is no 'error' event listener then throw.
if (type === 'error') {
if (!this._events.error ||
(isObject(this._events.error) && !this._events.error.length)) {
er = arguments[1];
if (er instanceof Error) {
throw er; // Unhandled 'error' event
}
throw TypeError('Uncaught, unspecified "error" event.');
}
}
handler = this._events[type];
if (isUndefined(handler))
return false;
if (isFunction(handler)) {
switch (arguments.length) {
// fast cases
case 1:
handler.call(this);
break;
case 2:
handler.call(this, arguments[1]);
break;
case 3:
handler.call(this, arguments[1], arguments[2]);
break;
// slower
default:
len = arguments.length;
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
handler.apply(this, args);
}
} else if (isObject(handler)) {
len = arguments.length;
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
listeners = handler.slice();
len = listeners.length;
for (i = 0; i < len; i++)
listeners[i].apply(this, args);
}
return true;
};
EventEmitter.prototype.addListener = function(type, listener) {
var m;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events)
this._events = {};
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (this._events.newListener)
this.emit('newListener', type,
isFunction(listener.listener) ?
listener.listener : listener);
if (!this._events[type])
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
else if (isObject(this._events[type]))
// If we've already got an array, just append.
this._events[type].push(listener);
else
// Adding the second element, need to change to array.
this._events[type] = [this._events[type], listener];
// Check for listener leak
if (isObject(this._events[type]) && !this._events[type].warned) {
var m;
if (!isUndefined(this._maxListeners)) {
m = this._maxListeners;
} else {
m = EventEmitter.defaultMaxListeners;
}
if (m && m > 0 && this._events[type].length > m) {
this._events[type].warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
this._events[type].length);
if (typeof console.trace === 'function') {
// not supported in IE 10
console.trace();
}
}
}
return this;
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.once = function(type, listener) {
if (!isFunction(listener))
throw TypeError('listener must be a function');
var fired = false;
function g() {
this.removeListener(type, g);
if (!fired) {
fired = true;
listener.apply(this, arguments);
}
}
g.listener = listener;
this.on(type, g);
return this;
};
// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
var list, position, length, i;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events || !this._events[type])
return this;
list = this._events[type];
length = list.length;
position = -1;
if (list === listener ||
(isFunction(list.listener) && list.listener === listener)) {
delete this._events[type];
if (this._events.removeListener)
this.emit('removeListener', type, listener);
} else if (isObject(list)) {
for (i = length; i-- > 0;) {
if (list[i] === listener ||
(list[i].listener && list[i].listener === listener)) {
position = i;
break;
}
}
if (position < 0)
return this;
if (list.length === 1) {
list.length = 0;
delete this._events[type];
} else {
list.splice(position, 1);
}
if (this._events.removeListener)
this.emit('removeListener', type, listener);
}
return this;
};
EventEmitter.prototype.removeAllListeners = function(type) {
var key, listeners;
if (!this._events)
return this;
// not listening for removeListener, no need to emit
if (!this._events.removeListener) {
if (arguments.length === 0)
this._events = {};
else if (this._events[type])
delete this._events[type];
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
for (key in this._events) {
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = {};
return this;
}
listeners = this._events[type];
if (isFunction(listeners)) {
this.removeListener(type, listeners);
} else {
// LIFO order
while (listeners.length)
this.removeListener(type, listeners[listeners.length - 1]);
}
delete this._events[type];
return this;
};
EventEmitter.prototype.listeners = function(type) {
var ret;
if (!this._events || !this._events[type])
ret = [];
else if (isFunction(this._events[type]))
ret = [this._events[type]];
else
ret = this._events[type].slice();
return ret;
};
EventEmitter.listenerCount = function(emitter, type) {
var ret;
if (!emitter._events || !emitter._events[type])
ret = 0;
else if (isFunction(emitter._events[type]))
ret = 1;
else
ret = emitter._events[type].length;
return ret;
};
function isFunction(arg) {
return typeof arg === 'function';
}
function isNumber(arg) {
return typeof arg === 'number';
}
function isObject(arg) {
return typeof arg === 'object' && arg !== null;
}
function isUndefined(arg) {
return arg === void 0;
}
},{}],6:[function(require,module,exports){
// shim for using process in browser
var process = module.exports = {};
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = setTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
if (currentQueue) {
currentQueue[queueIndex].run();
}
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
clearTimeout(timeout);
}
process.nextTick = function (fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
setTimeout(drainQueue, 0);
}
};
// v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
},{}],7:[function(require,module,exports){
(function (process){
/**
* @license
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/**
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** */
exports.notifications = {
/**
* Called after patch has compleated with any Nodes that have been created
* and added to the DOM.
* @type {?function(Array<!Node>)}
*/
nodesCreated: null,
/**
* Called after patch has compleated with any Nodes that have been removed
* from the DOM.
* Note it's an applications responsibility to handle any childNodes.
* @type {?function(Array<!Node>)}
*/
nodesDeleted: null
};
/**
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Similar to the built-in Treewalker class, but simplified and allows direct
* access to modify the currentNode property.
* @param {!Element|!DocumentFragment} node The root Node of the subtree the
* walker should start traversing.
* @constructor
*/
function TreeWalker(node) {
/**
* Keeps track of the current parent node. This is necessary as the traversal
* methods may traverse past the last child and we still need a way to get
* back to the parent.
* @const @private {!Array<!Node>}
*/
this.stack_ = [];
/**
* @const {!Element|!DocumentFragment}
*/
this.root = node;
/**
* @type {?Node}
*/
this.currentNode = node;
}
/**
* @return {!Node} The current parent of the current location in the subtree.
*/
TreeWalker.prototype.getCurrentParent = function () {
return this.stack_[this.stack_.length - 1];
};
/**
* Changes the current location the firstChild of the current location.
*/
TreeWalker.prototype.firstChild = function () {
this.stack_.push(this.currentNode);
this.currentNode = this.currentNode.firstChild;
};
/**
* Changes the current location the nextSibling of the current location.
*/
TreeWalker.prototype.nextSibling = function () {
this.currentNode = this.currentNode.nextSibling;
};
/**
* Changes the current location the parentNode of the current location.
*/
TreeWalker.prototype.parentNode = function () {
this.currentNode = this.stack_.pop();
};
/**
* Keeps track of the state of a patch.
* @param {!Element|!DocumentFragment} node The root Node of the subtree the
* is for.
* @param {?Context} prevContext The previous context.
* @constructor
*/
function Context(node, prevContext) {
/**
* @const {TreeWalker}
*/
this.walker = new TreeWalker(node);
/**
* @const {Document}
*/
this.doc = node.ownerDocument;
/**
* Keeps track of what namespace to create new Elements in.
* @private
* @const {!Array<(string|undefined)>}
*/
this.nsStack_ = [undefined];
/**
* @const {?Context}
*/
this.prevContext = prevContext;
/**
* @type {(Array<!Node>|undefined)}
*/
this.created = exports.notifications.nodesCreated && [];
/**
* @type {(Array<!Node>|undefined)}
*/
this.deleted = exports.notifications.nodesDeleted && [];
}
/**
* @return {(string|undefined)} The current namespace to create Elements in.
*/
Context.prototype.getCurrentNamespace = function () {
return this.nsStack_[this.nsStack_.length - 1];
};
/**
* @param {string=} namespace The namespace to enter.
*/
Context.prototype.enterNamespace = function (namespace) {
this.nsStack_.push(namespace);
};
/**
* Exits the current namespace
*/
Context.prototype.exitNamespace = function () {
this.nsStack_.pop();
};
/**
* @param {!Node} node
*/
Context.prototype.markCreated = function (node) {
if (this.created) {
this.created.push(node);
}
};
/**
* @param {!Node} node
*/
Context.prototype.markDeleted = function (node) {
if (this.deleted) {
this.deleted.push(node);
}
};
/**
* Notifies about nodes that were created during the patch opearation.
*/
Context.prototype.notifyChanges = function () {
if (this.created && this.created.length > 0) {
exports.notifications.nodesCreated(this.created);
}
if (this.deleted && this.deleted.length > 0) {
exports.notifications.nodesDeleted(this.deleted);
}
};
/**
* The current context.
* @type {?Context}
*/
var context;
/**
* Enters a new patch context.
* @param {!Element|!DocumentFragment} node
*/
var enterContext = function (node) {
context = new Context(node, context);
};
/**
* Restores the previous patch context.
*/
var restoreContext = function () {
context = context.prevContext;
};
/**
* Gets the current patch context.
* @return {?Context}
*/
var getContext = function () {
return context;
};
/**
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A cached reference to the hasOwnProperty function.
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* A cached reference to the create function.
*/
var create = Object.create;
/**
* Used to prevent property collisions between our "map" and its prototype.
* @param {!Object<string, *>} map The map to check.
* @param {string} property The property to check.
* @return {boolean} Whether map has property.
*/
var has = function (map, property) {
return hasOwnProperty.call(map, property);
};
/**
* Creates an map object without a prototype.
* @return {!Object}
*/
var createMap = function () {
return create(null);
};
/**
* Keeps track of information needed to perform diffs for a given DOM node.
* @param {!string} nodeName
* @param {?string=} key
* @constructor
*/
function NodeData(nodeName, key) {
/**
* The attributes and their values.
* @const
*/
this.attrs = createMap();
/**
* An array of attribute name/value pairs, used for quickly diffing the
* incomming attributes to see if the DOM node's attributes need to be
* updated.
* @const {Array<*>}
*/
this.attrsArr = [];
/**
* The incoming attributes for this Node, before they are updated.
* @const {!Object<string, *>}
*/
this.newAttrs = createMap();
/**
* The key used to identify this node, used to preserve DOM nodes when they
* move within their parent.
* @const
*/
this.key = key;
/**
* Keeps track of children within this node by their key.
* {?Object<string, !Element>}
*/
this.keyMap = null;
/**
* Whether or not the keyMap is currently valid.
* {boolean}
*/
this.keyMapValid = true;
/**
* The last child to have been visited within the current pass.
* @type {?Node}
*/
this.lastVisitedChild = null;
/**
* The node name for this node.
* @const {string}
*/
this.nodeName = nodeName;
/**
* @type {?string}
*/
this.text = null;
}
/**
* Initializes a NodeData object for a Node.
*
* @param {Node} node The node to initialize data for.
* @param {string} nodeName The node name of node.
* @param {?string=} key The key that identifies the node.
* @return {!NodeData} The newly initialized data object
*/
var initData = function (node, nodeName, key) {
var data = new NodeData(nodeName, key);
node['__incrementalDOMData'] = data;
return data;
};
/**
* Retrieves the NodeData object for a Node, creating it if necessary.
*
* @param {Node} node The node to retrieve the data for.
* @return {!NodeData} The NodeData for this Node.
*/
var getData = function (node) {
var data = node['__incrementalDOMData'];
if (!data) {
var nodeName = node.nodeName.toLowerCase();
var key = null;
if (node instanceof Element) {
key = node.getAttribute('key');
}
data = initData(node, nodeName, key);
}
return data;
};
/**
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.symbols = {
default: '__default',
placeholder: '__placeholder'
};
/**
* Applies an attribute or property to a given Element. If the value is null
* or undefined, it is removed from the Element. Otherwise, the value is set
* as an attribute.
* @param {!Element} el
* @param {string} name The attribute's name.
* @param {?(boolean|number|string)=} value The attribute's value.
*/
exports.applyAttr = function (el, name, value) {
if (value == null) {
el.removeAttribute(name);
} else {
el.setAttribute(name, value);
}
};
/**
* Applies a property to a given Element.
* @param {!Element} el
* @param {string} name The property's name.
* @param {*} value The property's value.
*/
exports.applyProp = function (el, name, value) {
el[name] = value;
};
/**
* Applies a style to an Element. No vendor prefix expansion is done for
* property names/values.
* @param {!Element} el
* @param {string} name The attribute's name.
* @param {string|Object<string,string>} style The style to set. Either a
* string of css or an object containing property-value pairs.
*/
var applyStyle = function (el, name, style) {
if (typeof style === 'string') {
el.style.cssText = style;
} else {
el.style.cssText = '';
var elStyle = el.style;
for (var prop in style) {
if (has(style, prop)) {
elStyle[prop] = style[prop];
}
}
}
};
/**
* Updates a single attribute on an Element.
* @param {!Element} el
* @param {string} name The attribute's name.
* @param {*} value The attribute's value. If the value is an object or
* function it is set on the Element, otherwise, it is set as an HTML
* attribute.
*/
var applyAttributeTyped = function (el, name, value) {
var type = typeof value;
if (type === 'object' || type === 'function') {
exports.applyProp(el, name, value);
} else {
exports.applyAttr(el, name, /** @type {?(boolean|number|string)} */value);
}
};
/**
* Calls the appropriate attribute mutator for this attribute.
* @param {!Element} el
* @param {string} name The attribute's name.
* @param {*} value The attribute's value.
*/
var updateAttribute = function (el, name, value) {
var data = getData(el);
var attrs = data.attrs;
if (attrs[name] === value) {
return;
}
var mutator = exports.attributes[name] || exports.attributes[exports.symbols.default];
mutator(el, name, value);
attrs[name] = value;
};
/**
* A publicly mutable object to provide custom mutators for attributes.
* @const {!Object<string, function(!Element, string, *)>}
*/
exports.attributes = createMap();
// Special generic mutator that's called for any attribute that does not
// have a specific mutator.
exports.attributes[exports.symbols.default] = applyAttributeTyped;
exports.attributes[exports.symbols.placeholder] = function () {};
exports.attributes['style'] = applyStyle;
var SVG_NS = 'http://www.w3.org/2000/svg';
/**
* Enters a tag, checking to see if it is a namespace boundary, and if so,
* updates the current namespace.
* @param {string} tag The tag to enter.
*/
var enterTag = function (tag) {
if (tag === 'svg') {
getContext().enterNamespace(SVG_NS);
} else if (tag === 'foreignObject') {
getContext().enterNamespace(undefined);
}
};
/**
* Exits a tag, checking to see if it is a namespace boundary, and if so,
* updates the current namespace.
* @param {string} tag The tag to enter.
*/
var exitTag = function (tag) {
if (tag === 'svg' || tag === 'foreignObject') {
getContext().exitNamespace();
}
};
/**
* Gets the namespace to create an element (of a given tag) in.
* @param {string} tag The tag to get the namespace for.
* @return {(string|undefined)} The namespace to create the tag in.
*/
var getNamespaceForTag = function (tag) {
if (tag === 'svg') {
return SVG_NS;
}
return getContext().getCurrentNamespace();
};
/**
* Creates an Element.
* @param {Document} doc The document with which to create the Element.
* @param {string} tag The tag for the Element.
* @param {?string=} key A key to identify the Element.
* @param {?Array<*>=} statics An array of attribute name/value pairs of
* the static attributes for the Element.
* @return {!Element}
*/
var createElement = function (doc, tag, key, statics) {
var namespace = getNamespaceForTag(tag);
var el;
if (namespace) {
el = doc.createElementNS(namespace, tag);
} else {
el = doc.createElement(tag);
}
initData(el, tag, key);
if (statics) {
for (var i = 0; i < statics.length; i += 2) {
updateAttribute(el, /** @type {!str