UNPKG

jaydata-core

Version:

Cross-platform HTML5 data-management, JavaScript Language Query (JSLQ) support for OData, SQLite, WebSQL, IndexedDB, YQL and Facebook (packaged for Node.JS)

672 lines (590 loc) 26 kB
var EventSubscriber = $data.Class.define("EventSubscriber", null, null, { constructor: function (handler, state, thisArg) { /// <param name="handler" type="Function"> /// <summary>event handler</summary> /// <signature> /// <param name="sender" type="$data.Entity" /> /// <param name="eventData" type="EventData" /> /// <param name="state" type="Object" /> /// </signature> /// </param> /// <param name="state" type="Object" optional="true">custom state object</param> /// <param name="thisArg" type="Object" optional="true">[i]this[/i] context for handler</param> /// /// <field name="handler" type="function($data.Entity sender, EventData eventData, Object state)">event handler</field> /// <field name="state" type="Object">custom state object</field> /// <field name="thisArg">[i]this[/i] context for handler</field> this.handler = handler; this.state = state; this.thisArg = thisArg; }, handler: {}, state: {}, thisArg: {} }); $data.Event = Event = $data.Class.define("$data.Event", null, null, { constructor: function (name, sender) { ///<param name="name" type="string">The name of the event</param> ///<param name="sender" type="Object">The originator/sender of the event. [this] in handlers will be set to this</param> var subscriberList = null; var parentObject = sender; function detachHandler(list, handler) { ///<param name="list" type="Array" elementType="EventSubscriber" /> ///<param name="handler" type="Function" /> list.forEach(function (item, index) { if (item.handler == handler) { list.splice(index, 1); } }); } this.attach = function (handler, state, thisArg) { ///<param name="handler" type="Function"> ///<signature> ///<param name="sender" type="Object" /> ///<param name="eventData" type="Object" /> ///<param name="state" type="Object" /> ///</signature> ///</param> ///<param name="state" type="Object" optional="true" /> ///<param name="thisArg" type="Object" optional="true" /> if (!subscriberList) { subscriberList = []; } subscriberList.push(new EventSubscriber(handler, state, thisArg || sender)); }; this.detach = function (handler) { detachHandler(subscriberList, handler); }; this.fire = function (eventData, snder) { var snd = snder || sender || this; //eventData.eventName = name; ///<value name="subscriberList type="Array" /> if (subscriberList) { subscriberList.forEach(function (subscriber) { ///<param name="subscriber" type="EventSubscriber" /> try { subscriber.handler.call(subscriber.thisArg, snd, eventData, subscriber.state); } catch(ex) { console.log("unhandled exception in event handler. exception suppressed"); console.dir(ex); } }); } }; this.fireCancelAble = function (eventData, snder) { var snd = snder || sender || this; //eventData.eventName = name; ///<value name="subscriberList type="Array" /> var isValid = true; if (subscriberList) { subscriberList.forEach(function (subscriber) { ///<param name="subscriber" type="EventSubscriber" /> try { isValid = isValid && (subscriber.handler.call(subscriber.thisArg, snd, eventData, subscriber.state) === false ? false : true); } catch (ex) { console.log("unhandled exception in event handler. exception suppressed"); console.dir(ex); } }); } return isValid; }; } }); var eventData = $data.Class.define("EventData", null, null, { eventName: {} }); var PropertyChangeEventData = $data.Class.define("PropertyChangeEventData", EventData, null, { constructor: function (propertyName, oldValue, newValue) { this.propertyName = propertyName; this.oldValue = oldValue; this.newValue = newValue; }, propertyName: {}, oldValue: {}, newValue: {} }); var PropertyValidationEventData = $data.Class.define("PropertyValidationEventData", EventData, null, { constructor: function (propertyName, oldValue, newValue, errors) { this.propertyName = propertyName; this.oldValue = oldValue; this.newValue = newValue; this.errors = errors; this.cancel = false; }, propertyName: {}, oldValue: {}, newValue: {}, errors: {}, cancel: {} }); $data.Entity = Entity = $data.Class.define("$data.Entity", null, null, { constructor: function (initData, newInstanceOptions) { /// <description> /// This class provide a light weight, object-relational interface between /// your javascript code and database. /// </description> /// /// <signature> /// <param name="initData" type="Object">initialization data</param> /// <example> /// var category = new $news.Types.Category({ Title: 'Tech' }); /// $news.context.Categories.add(category); /// </example> /// </signature> /// /// <field name="initData" type="Object">initialization data</field> /// <field name="context" type="$data.EntityContext"></field> /// <field name="propertyChanging" type="$data.Event"></field> /// <field name="propertyChanged" type="$data.Event"></field> /// <field name="propertyValidationError" type="$data.Event"></field> /// <field name="isValidated" type="Boolean">Determines the current $data.Entity is validated.</field> /// <field name="ValidationErrors" type="Array">array of $data.Validation.ValidationError</field> /// <field name="ValidationErrors" type="Array">array of MemberDefinition</field> /// <field name="entityState" type="Integer"></field> /// <field name="changedProperties" type="Array">array of MemberDefinition</field> this.initData = {}; var thisType = this.getType(); if (thisType.__copyPropertiesToInstance) { $data.typeSystem.writePropertyValues(this); } var ctx = null; this.context = ctx; if ("setDefaultValues" in thisType) { if (!newInstanceOptions || newInstanceOptions.setDefaultValues !== false) { if (!initData || Object.keys(initData).length < 1) { initData = thisType.setDefaultValues(initData); } } } if (typeof initData === "object") { var typeMemDefs = thisType.memberDefinitions; var memDefNames = typeMemDefs.getPublicMappedPropertyNames(); for (var i in initData) { if (memDefNames.indexOf(i) > -1) { var memberDef = typeMemDefs.getMember(i); var type = Container.resolveType(memberDef.type); var value = initData[i]; if (memberDef.concurrencyMode === $data.ConcurrencyMode.Fixed) { this.initData[i] = value; } else { if (newInstanceOptions && newInstanceOptions.converters) { var converter = newInstanceOptions.converters[Container.resolveName(type)]; if (converter) value = converter(value); } this.initData[i] = Container.convertTo(value, type, memberDef.elementType, newInstanceOptions); } } } } if (newInstanceOptions && newInstanceOptions.entityBuilder) { newInstanceOptions.entityBuilder(this, thisType.memberDefinitions.asArray(), thisType); } this.changedProperties = undefined; this.entityState = undefined; }, toString: function () { /// <summary>Returns a string that represents the current $data.Entity</summary> /// <returns type="String"/> return this.getType().fullName + "(" + (this.Id || this.Name || '') + ")" }, toJSON: function () { /// <summary>Creates pure JSON object from $data.Entity.</summary> /// <returns type="Object">JSON representation</returns> var result = {}; var self = this; this.getType().memberDefinitions.getPublicMappedProperties().forEach(function (memDef) { if (self[memDef.name] instanceof Date && memDef.type && Container.resolveType(memDef.type) === $data.DateTimeOffset) { result[memDef.name] = new $data.DateTimeOffset(self[memDef.name]); } else { result[memDef.name] = self[memDef.name]; } }); return result; }, equals: function (entity) { /// <summary>Determines whether the specified $data.Entity is equal to the current $data.Entity.</summary> /// <returns type="Boolean">[b]true[/b] if the specified $data.Entity is equal to the current $data.Entity; otherwise, [b]false[/b].</returns> if (entity.getType() !== this.getType()) { return false; } var entityPk = this.getType().memberDefinitions.getKeyProperties(); for (var i = 0; i < entityPk.length; i++) { if (this[entityPk[i].name] != entity[entityPk[i].name]) { return false; } } return true; }, propertyChanging: { dataType: $data.Event, storeOnObject: true, monitorChanges: false, notMapped: true, enumerable: false, prototypeProperty: true, get: function () { if (!this._propertyChanging) this._propertyChanging = new Event('propertyChanging', this); return this._propertyChanging; }, set: function (value) { this._propertyChanging = value; } }, propertyChanged: { dataType: $data.Event, storeOnObject: true, monitorChanges: false, notMapped: true, enumerable: false, prototypeProperty: true, get: function () { if (!this._propertyChanged) this._propertyChanged = new Event('propertyChanged', this); return this._propertyChanged; }, set: function (value) { this._propertyChanged = value; } }, propertyValidationError: { dataType: $data.Event, storeOnObject: true, monitorChanges: false, notMapped: true, enumerable: false, prototypeProperty: true, get: function () { if (!this._propertyValidationError) this._propertyValidationError = new Event('propertyValidationError', this); return this._propertyValidationError; }, set: function (value) { this._propertyValidationError = value; } }, // protected storeProperty: function (memberDefinition, value) { /// <param name="memberDefinition" type="MemberDefinition" /> /// <param name="value" /> if (memberDefinition.concurrencyMode !== $data.ConcurrencyMode.Fixed) { value = Container.convertTo(value, memberDefinition.type, memberDefinition.elementType); } var eventData = null; if (memberDefinition.monitorChanges != false && (this._propertyChanging || this._propertyChanged || "instancePropertyChanged" in this.constructor)) { var origValue = this[memberDefinition.name]; eventData = new PropertyChangeEventData(memberDefinition.name, origValue, value); if (this._propertyChanging) this.propertyChanging.fire(eventData); } if (memberDefinition.monitorChanges != false && (this._propertyValidationError || "instancePropertyValidationError" in this.constructor)) { var errors = $data.Validation.Entity.ValidateEntityField(this, memberDefinition, value); if (errors.length > 0) { var origValue = this[memberDefinition.name]; var errorEventData = new PropertyValidationEventData(memberDefinition.name, origValue, value, errors); if (this._propertyValidationError) this.propertyValidationError.fire(errorEventData); if ("instancePropertyValidationError" in this.constructor) this.constructor["instancePropertyValidationError"].fire(errorEventData, this); if (errorEventData.cancel == true) return; } } if (memberDefinition.storeOnObject == true) { //TODO refactor to Base.getBackingFieldName var backingFieldName = "_" + memberDefinition.name; this[backingFieldName] = value; } else { this.initData[memberDefinition.name] = value; } this.isValidated = false; if (memberDefinition.monitorChanges != false && this.entityState == $data.EntityState.Unchanged) this.entityState = $data.EntityState.Modified; this._setPropertyChanged(memberDefinition); if (memberDefinition.monitorChanges != false) { //if (!this.changedProperties) { // this.changedProperties = []; //} //if (!this.changedProperties.some(function (memDef) { return memDef.name == memberDefinition.name })) // this.changedProperties.push(memberDefinition); if (this._propertyChanged) this.propertyChanged.fire(eventData); //TODO mixin framework if ("instancePropertyChanged" in this.constructor) { this.constructor["instancePropertyChanged"].fire(eventData, this); } } }, _setPropertyChanged: function (memberDefinition) { if (memberDefinition.monitorChanges != false) { if (!this.changedProperties) { this.changedProperties = []; } if (!this.changedProperties.some(function (memDef) { return memDef.name == memberDefinition.name })) this.changedProperties.push(memberDefinition); } }, // protected retrieveProperty: function (memberDefinition) { /// <param name="memberDefinition" type="MemberDefinition" /> if (memberDefinition.storeOnObject == true) { //TODO refactor to Base.getBackingFieldName var backingFieldName = "_" + memberDefinition.name; return this[backingFieldName]; } else { return this.initData[memberDefinition.name]; } }, // protected getProperty: function (memberDefinition, callback, tran) { /// <summary>Retrieve value of member</summary> /// <param name="memberDefinition" type="MemberDefinition" /> /// <param name="callback" type="Function"> /// <signature> /// <param name="value" /> /// </signature> /// </param> /// <returns>value associated for [i]memberDefinition[/i]</returns> callback = $data.typeSystem.createCallbackSetting(callback); if (this[memberDefinition.name] != undefined) { if (tran instanceof $data.Transaction) callback.success(this[memberDefinition.name], tran); else callback.success(this[memberDefinition.name]); return; } var context = this.context; if (!this.context) { try { var that = this; var storeToken = this.storeToken || this.getType().storeToken; if (storeToken && typeof storeToken.factory === 'function') { var ctx = storeToken.factory(); return ctx.onReady().then(function (context) { return context.loadItemProperty(that, memberDefinition, callback); }); } } catch (e) { } Guard.raise(new Exception('Entity not in context', 'Invalid operation')); } else { return context.loadItemProperty(this, memberDefinition, callback, tran); } }, // protected setProperty: function (memberDefinition, value, callback, tran) { /// <param name="memberDefinition" type="MemberDefinition" /> /// <param name="value" /> /// <param name="callback" type="Function">done</param> this[memberDefinition.name] = value; //callback = $data.typeSystem.createCallbackSetting(callback); var pHandler = new $data.PromiseHandler(); callback = pHandler.createCallback(callback); callback.success(this[memberDefinition.name]); return pHandler.getPromise(); }, isValid: function () { /// <summary>Determines the current $data.Entity is validated and valid.</summary> /// <returns type="Boolean" /> if (!this.isValidated) { this.ValidationErrors = $data.Validation.Entity.ValidateEntity(this); this.isValidated = true; } return this.ValidationErrors.length == 0; }, isValidated: { dataType: "bool", storeOnObject: true, monitorChanges: false, notMapped: true, enumerable: false, value: false }, ValidationErrors: { dataType: Array, elementType: $data.Validation.ValidationError, storeOnObject: true, monitorChanges: true, notMapped: true, enumerable: false }, resetChanges: function () { /// <summary>reset changes</summary> delete this._changedProperties; }, changedProperties: { dataType: Array, elementType: window["MemberDefinition"], storeOnObject: true, monitorChanges: false, notMapped: true, enumerable: false }, entityState: { dataType: "integer", storeOnObject: true, monitorChanges: false, notMapped: true, enumerable: false }, /* toJSON: function () { if (this.context) { var itemType = this.getType(); var storageModel = this.context._storageModel[itemType.name]; var o = new Object(); for (var property in this) { if (typeof this[property] !== "function") { var excludedFields = storageModel.Associations.every(function (association) { return association.FromPropertyName == property && (association.FromMultiplicity == "0..1" || association.FromMultiplicity == "1"); }, this); if (!excludedFields) { o[property] = this[property]; } } } return o; } return this; } */ //, //onReady: function (callback) { // this.__onReadyList = this.__onReadyList || []; // this.__onReadyList.push(callback); //}, remove: function () { if ($data.ItemStore && 'EntityInstanceRemove' in $data.ItemStore) return $data.ItemStore.EntityInstanceRemove.apply(this, arguments); else throw 'not implemented'; //todo }, save: function () { if ($data.ItemStore && 'EntityInstanceSave' in $data.ItemStore) return $data.ItemStore.EntityInstanceSave.apply(this, arguments); else throw 'not implemented'; //todo }, refresh: function () { if ($data.ItemStore && 'EntityInstanceSave' in $data.ItemStore) return $data.ItemStore.EntityInstanceRefresh.apply(this, arguments); else throw 'not implemented'; //todo }, storeToken: { type: Object, monitorChanges: false, notMapped: true, storeOnObject: true }, getFieldUrl: function (field) { if (this.context) { return this.context.getFieldUrl(this, field); } else if (this.getType().storeToken && typeof this.getType().storeToken.factory === 'function') { var context = this.getType().storeToken.factory(); return context.getFieldUrl(this, field); } else if (this.getType().storeToken){ try { var ctx = $data.ItemStore._getContextPromise('default', this.getType()); if (ctx instanceof $data.EntityContext) { return ctx.getFieldUrl(this, field); } } catch (e) { } } return '#'; } }, { //create get_[property] and set_[property] functions for properties __setPropertyfunctions: { value: true, notMapped: true, enumerable: false, storeOnObject: true }, //copy public properties to current instance __copyPropertiesToInstance: { value: false, notMapped: true, enumerable: false, storeOnObject: true }, inheritedTypeProcessor: function (type) { if ($data.ItemStore && 'EntityInheritedTypeProcessor' in $data.ItemStore) $data.ItemStore.EntityInheritedTypeProcessor.apply(this, arguments); //default value setter method factory type.defaultValues = {}; type.memberDefinitions.asArray().forEach(function (pd) { if (pd.hasOwnProperty("defaultValue")) { type.defaultValues[pd.name] = pd.defaultValue; } }); if (Object.keys(type.defaultValues).length > 0) { type.setDefaultValues = function (initData, instance) { initData = initData || {}; var dv = type.defaultValues; for (var n in dv) { if (!(n in initData)) { var value = dv[n]; if ("function" === typeof value) { initData[n] = dv[n](n, instance); } else { initData[n] = dv[n]; } } } return initData; } } }, //Type Events addEventListener: function(eventName, fn) { var delegateName = "on" + eventName; if (!(delegateName in this)) { this[delegateName] = new $data.Event(eventName, this); } this[delegateName].attach(fn); }, removeEventListener: function(eventName, fn) { var delegateName = "on" + eventName; if (!(delegateName in this)) { return; } this[delegateName].detach(fn); }, raiseEvent: function(eventName, data) { var delegateName = "on" + eventName; if (!(delegateName in this)) { return; } this[delegateName].fire(data); }, getFieldNames: function () { return this.memberDefinitions.getPublicMappedPropertyNames(); }, 'from$data.Object': function (value, type, t, options) { if (!Object.isNullOrUndefined(value)) { var newInstanceOptions; if (options && options.converters) { newInstanceOptions = { converters: options.converters } } return new this(value, newInstanceOptions); } else { return value; } } }); $data.define = function (name, container, definition) { if (container && !(container instanceof $data.ContainerClass)) { definition = container; container = undefined; } if (!definition) { throw new Error("json object type is not supported yet"); } var _def = {}; var hasKey = false; var keyFields = []; Object.keys(definition).forEach(function (fieldName) { var propDef = definition[fieldName]; if (typeof propDef === 'object' && ("type" in propDef || "get" in propDef || "set" in propDef)) { _def[fieldName] = propDef; if (propDef.key) { keyFields.push(propDef); } if (("get" in propDef || "set" in propDef) && (!('notMapped' in propDef) || propDef.notMapped === true)) { propDef.notMapped = true; propDef.storeOnObject = true; } if ("get" in propDef && !("set" in propDef)) { propDef.set = function () { }; } else if ("set" in propDef && !("get" in propDef)) { propDef.get = function () { }; } } else { _def[fieldName] = { type: propDef }; } }); if (keyFields.length < 1) { var keyProp; switch (true) { case "id" in _def: keyProp = "id"; break; case "Id" in _def: keyProp = "Id" break; case "ID" in _def: keyProp = "ID" break; } if (keyProp) { _def[keyProp].key = true; var propTypeName = $data.Container.resolveName(_def[keyProp].type); _def[keyProp].computed = true; //if ("$data.Number" === propTypeName || "$data.Integer" === propTypeName) { //} } else { _def.Id = { type: "int", key: true, computed: true } } } var entityType = $data.Entity.extend(name, container, _def); return entityType; } $data.implementation = function (name) { return Container.resolveType(name); };