starplate
Version:
View engine built on incremental-dom
335 lines (276 loc) • 8.46 kB
JavaScript
;
/**
* 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;