UNPKG

este-library-oldschool

Version:

Library for github.com/steida/este.git

603 lines (556 loc) 13.9 kB
// Generated by github.com/steida/coffee2closure 900.1.18 /** @fileoverview Base class for models with attributes and schema. Use model instead of plain json object and you will be able to listen model changes, validate model, and serialize/deserialize it. Features: - setters, getters, validators - 'add', 'remove', 'change', 'sort', 'update' events - JSON serialization/deserialization Why not plain objects with properties? - strings are better for uncompiled attributes from DOM, storages etc. - http://www.devthought.com/2012/01/18/an-object-is-not-a-hash How to modify complex properties? ```user.set 'tags', (tags) -> tags.push 'some tag'``` @see /demos/model/model.html @see /demos/app/todomvc/index.html */ var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.superClass_ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; goog.provide('este.Model'); goog.provide('este.Model.EventType'); goog.provide('este.Model.Event'); goog.require('este.Base'); goog.require('este.json'); goog.require('este.model.getters'); goog.require('este.model.setters'); goog.require('goog.events.Event'); goog.require('goog.object'); goog.require('goog.ui.IdGenerator'); goog.require('este.validators.Base'); goog.require('este.validators.digits'); goog.require('este.validators.email'); goog.require('este.validators.exclusion'); goog.require('este.validators.format'); goog.require('este.validators.inclusion'); goog.require('este.validators.max'); goog.require('este.validators.maxLength'); goog.require('este.validators.min'); goog.require('este.validators.minLength'); goog.require('este.validators.number'); goog.require('este.validators.range'); goog.require('este.validators.rangeLength'); goog.require('este.validators.required'); goog.require('este.validators.url'); /** @param {Object=} json @param {function(): string=} idGenerator @constructor @extends {este.Base} */ este.Model = function(json, idGenerator) { var defaults; este.Model.superClass_.constructor.call(this); this.attributes = {}; if (this.schema == null) { this.schema = {}; } defaults = this.getResolvedDefaults(); if (defaults) { this.setAttributes(defaults); } if (json) { this.setAttributes(json); } this.ensureClientId(idGenerator); } extend(este.Model, superClass); /** @enum {string} */ este.Model.EventType = { CHANGE: 'change', ADD: 'add', REMOVE: 'remove', SORT: 'sort', UPDATE: 'update' }; /** @param {?} a @param {?} b @suppress {missingProperties} Because runtime detection. */ este.Model.equal = function(a, b) { if (!a || !b) { return a === b; } if (a != null ? a.toJson : void 0) { a = este.json.stringify(a.toJson()); } if (b != null ? b.toJson : void 0) { b = este.json.stringify(b.toJson()); } return este.json.equal(a, b); }; /** http://www.restapitutorial.com/lessons/restfulresourcenaming.html Url has to start with '/'. Function type is usefull for inheritance. @type {string|function(): string} @protected */ este.Model.prototype.url = '/models'; /** @type {Object} @protected */ este.Model.prototype.defaults = null; /** @type {Object} @protected */ este.Model.prototype.schema = null; /** Custom ID attribute, ex. 'userId' or '_id'. Nested keys are supported, ex. '_id.$oid'. @type {string} @protected */ este.Model.prototype.idAttribute = 'id'; /** @type {string} @protected */ este.Model.prototype.id = ''; /** @type {Object} @protected */ este.Model.prototype.attributes = null; /** @param {string|number} id */ este.Model.prototype.setId = function(id) { var i, idAttribute, idAttributes, j, json, len, nested; json = {}; nested = json; idAttributes = this.idAttribute.split('.'); for (i = j = 0, len = idAttributes.length; j < len; i = ++j) { idAttribute = idAttributes[i]; if (i === idAttributes.length - 1) { nested[idAttribute] = id; } else { nested = nested[idAttribute] = {}; } } return this.set(json); }; /** @return {string} */ este.Model.prototype.getId = function() { return this.id; }; /** @return {string} */ este.Model.prototype.getUrl = function() { var url; url = this.url; if (goog.isFunction(url)) { url = url(); } return url; }; /** Generates URLs of the form: "/[collection.getUrl()]/[getId()]" if collection "/[model.url]/[getId()]" without collection @param {este.Collection=} collection @return {string} */ este.Model.prototype.createUrl = function(collection) { var id, url; url = collection ? collection.getUrl() : this.getUrl(); id = this.getId(); if (!id) { return url; } return url + '/' + id; }; /** Set attributes and dispatch events on model change. Example: ```coffee model.set 'foo', 1 model set 'foo': 1, 'bla': 2 ``` @param {Object|string} json Object of key value pairs or string key. @param {*=} value value or nothing. */ este.Model.prototype.set = function(json, value) { var _json, changes; if (typeof json === 'string') { _json = {}; _json[json] = value; json = _json; } changes = this.getChanges(json); if (!changes) { return; } this.setAttributes(changes); this.dispatchChangeEvent(changes); }; /** @param {Object} json @return {Object} @protected */ este.Model.prototype.getChanges = function(json) { var changes, clone, key, ref, set, value; changes = null; for (key in json) { value = json[key]; if (goog.isFunction(value)) { clone = this.getClone(key); value(clone); value = clone; } set = (ref = this.schema[key]) != null ? ref.set : void 0; if (set) { value = set(value); } if (este.Model.equal(value, this.get(key))) { continue; } if (changes == null) { changes = {}; } changes[key] = value; } return changes; }; /** @param {Object} json @protected */ este.Model.prototype.setAttributes = function(json) { var $key, currentValue, idAttributes, key, value; idAttributes = this.idAttribute.split('.'); for (key in json) { value = json[key]; $key = this.getKey(key); currentValue = this.attributes[$key]; if (key === idAttributes[0]) { this.setIdAttribute(value, currentValue, idAttributes); } if (key === '_cid' && (currentValue != null)) { goog.asserts.fail('Model _cid is immutable.'); } this.attributes[$key] = value; if (value instanceof este.Base) { value.addParent(this); } } }; /** Returns model attribute(s). @param {string|Array.<string>} key @return {*|Object.<string, *>} */ este.Model.prototype.get = function(key) { var get, j, json, k, len, meta, ref, ref1, value; if (typeof key !== 'string') { json = {}; for (j = 0, len = key.length; j < len; j++) { k = key[j]; json[k] = this.get(k); } return json; } meta = (ref = this.schema[key]) != null ? ref['meta'] : void 0; if (meta) { return meta(this); } value = this.attributes[this.getKey(key)]; get = (ref1 = this.schema[key]) != null ? ref1.get : void 0; if (get) { return get(value); } return value; }; /** Returns cloned model attribute(s). @param {string|Array.<string>} key @return {*|Object.<string, *>} */ este.Model.prototype.getClone = function(key) { return goog.object.unsafeClone(this.get(key)); }; /** @param {string} key @return {boolean} */ este.Model.prototype.has = function(key) { var ref; if (this.getKey(key) in this.attributes) { return true; } if ((ref = this.schema[key]) != null ? ref['meta'] : void 0) { return true; } return false; }; /** @param {string} key @return {boolean} true if removed */ este.Model.prototype.remove = function(key) { var _key, changed, value; _key = this.getKey(key); if (!(_key in this.attributes)) { return false; } value = this.attributes[_key]; if (value instanceof este.Base) { value.removeParent(this); } delete this.attributes[_key]; changed = {}; changed[key] = value; this.dispatchChangeEvent(changed); return true; }; /** Return a model JSON representation, which can be used for persistence, serialization, or model view rendering. @param {boolean=} raw If true, _cid, metas, and getters are ignored. @return {Object} */ este.Model.prototype.toJson = function(raw) { var $key, attr, json, key, meta, ref, ref1, value; json = {}; ref = this.attributes; for ($key in ref) { value = ref[$key]; key = $key.substring(1); if (raw && key === '_cid') { continue; } attr = raw ? value : this.get(key); if (attr != null ? attr.toJson : void 0) { json[key] = attr.toJson(raw); } else { json[key] = attr; } } if (!raw) { ref1 = this.schema; for (key in ref1) { value = ref1[key]; meta = value != null ? value['meta'] : void 0; if (!meta) { continue; } json[key] = meta(this); } } return json; }; /** Validate model recursively. @return {Array.<este.validators.Base>} */ este.Model.prototype.validate = function() { var allErrors, errors, key, keys, ref, value, values; keys = (function() { var ref, results; ref = this.schema; results = []; for (key in ref) { value = ref[key]; if (value != null ? value['validators'] : void 0) { results.push(key); } } return results; }).call(this); values = /** @type {Object} */(this.get(keys)); allErrors = this.getErrors(values) || []; ref = this.attributes; for (key in ref) { value = ref[key]; if (!value || !goog.isFunction(value.validate)) { continue; } errors = value.validate(); if (!errors) { continue; } allErrors.push.apply(allErrors, errors); } if (allErrors.length) { return allErrors; } return null; }; /** Prefix because http://www.devthought.com/2012/01/18/an-object-is-not-a-hash @param {string} key @return {string} */ este.Model.prototype.getKey = function(key) { return '$' + key; }; /** Returns true if model has no ID. @return {boolean} */ este.Model.prototype.isNew = function() { return !this.getId(); }; /** @return {Object} @protected */ este.Model.prototype.getResolvedDefaults = function() { if (!this.defaults) { return {}; } return goog.object.map(this.defaults, function(value, key) { switch (goog.typeOf(value)) { case 'function': return value(); case 'object': case 'array': return goog.object.unsafeClone(value); default: return value; } }); }; /** @protected */ este.Model.prototype.setIdAttribute = function(value, currentValue, idAttributes) { var nestedId; if (currentValue != null) { goog.asserts.fail('Model id is immutable.'); } if (idAttributes.length === 1) { this.id = value.toString(); } else { nestedId = goog.object.getValueByKeys(value, idAttributes.slice(1)); this.id = nestedId.toString(); } }; /** @param {Object} json key is attr, value is its value @return {Array.<este.validators.Base>} @protected */ este.Model.prototype.getErrors = function(json) { var errors, j, key, len, ref, validator, validatorFactory, validators, value; errors = []; for (key in json) { value = json[key]; validators = (ref = this.schema[key]) != null ? ref['validators'] : void 0; if (!validators) { continue; } for (j = 0, len = validators.length; j < len; j++) { validatorFactory = validators[j]; validator = validatorFactory(); validator.model = this; validator.key = key; validator.value = value; if (!validator.isValidable()) { continue; } if (validator.validate(value)) { continue; } errors.push(validator); } } if (errors.length) { return errors; } return null; }; /** @param {Object} changed @protected */ este.Model.prototype.dispatchChangeEvent = function(changed) { var changeEvent; changeEvent = new este.Model.Event(este.Model.EventType.CHANGE, this); changeEvent.model = this; changeEvent.changed = changed; return this.dispatchModelEvent(changeEvent); }; /** @param {este.Model.Event} e @protected */ este.Model.prototype.dispatchModelEvent = function(e) { var updateEvent; this.dispatchEvent(e); updateEvent = new este.Model.Event(este.Model.EventType.UPDATE, this); updateEvent.origin = e; return this.dispatchEvent(updateEvent); }; /** @param {function(): string=} idGenerator @protected */ este.Model.prototype.ensureClientId = function(idGenerator) { var _cid; if (this.get('_cid')) { return; } _cid = idGenerator ? idGenerator() : goog.ui.IdGenerator.getInstance().getNextUniqueId(); return this.setAttributes({ '_cid': _cid }); }; /** @fileoverview este.Model.Event. */ /** @param {string} type Event Type. @param {goog.events.EventTarget} target @constructor @extends {goog.events.Event} */ este.Model.Event = function(type, target) { este.Model.Event.superClass_.constructor.call(this, type, target); } extend(este.Model.Event, superClass); /** @type {este.Model} */ este.Model.Event.prototype.model = null; /** Changed model attributes. @type {Object} */ este.Model.Event.prototype.changed = null; /** Added models. @type {Array.<este.Model>} */ este.Model.Event.prototype.added = null; /** Removed models. @type {Array.<este.Model>} */ este.Model.Event.prototype.removed = null; /** @type {este.Model.Event} */ este.Model.Event.prototype.origin = null; /** * @type {Object} */ este.Model.Event.prototype.data = null;