UNPKG

tgi-store-mongodb

Version:
1,382 lines (1,363 loc) 121 kB
/**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/misc/lib-header **/ (function () { "use strict"; var root = this; /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/tgi-core.source.js **/ var TGI = { CORE: function () { return { version: '0.4.43', Application: Application, Attribute: Attribute, Command: Command, Delta: Delta, Interface: Interface, List: List, Log: Log, MemoryStore: MemoryStore, Message: Message, Model: Model, Presentation: Presentation, Procedure: Procedure, REPLInterface: REPLInterface, Request: Request, Session: Session, Store: Store, Text: Text, Transport: Transport, User: User, View: View, Workspace: Workspace, inheritPrototype: inheritPrototype, getInvalidProperties: getInvalidProperties, getConstructorFromModelType: getConstructorFromModelType, createModelFromModelType: createModelFromModelType, trim: trim, ltrim: ltrim, rtrim: rtrim, left: left, center: center, right: right, lpad: lpad, rpad: rpad, cpad: cpad, contains: contains }; } }; /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/tgi-core-attribute.source.js */ /** * Constructor */ function Attribute(args, arg2) { var splitTypes; // For String(30) type if (false === (this instanceof Attribute)) throw new Error('new operator required'); if (typeof args == 'string') { var quickName = args; args = {}; args.name = quickName; if (typeof arg2 == 'string') { args.type = arg2; } } args = args || {}; this.name = args.name || null; this.label = args.label || args.name; if (this.label) this.label = this.label.charAt(0).toUpperCase() + this.label.slice(1); this.type = args.type || 'String'; splitTypes = function (str) { // for String(30) remove right of ( var tmpSplit = str.split('('); tmpSplit[1] = parseInt(tmpSplit[1]); return tmpSplit; }(this.type); this.type = splitTypes[0]; this.hint = args.hint || {}; if (args.hidden !== undefined) this.hidden = args.hidden; this.validationRule = args.validationRule || {}; var unusedProperties = []; var standardProperties = ['name', 'type', 'label', 'hint', 'hidden', 'value', 'validationRule']; switch (this.type) { case 'ID': unusedProperties = getInvalidProperties(args, standardProperties); this.value = args.value || null; break; case 'String': unusedProperties = getInvalidProperties(args, standardProperties.concat(['placeHolder', 'quickPick', 'size'])); this.size = splitTypes[1] ? splitTypes[1] : typeof args.size == 'number' ? args.size : args.size || 50; this.value = args.value || null; if (args.quickPick) this.quickPick = args.quickPick; this.placeHolder = args.placeHolder || null; break; case 'Date': unusedProperties = getInvalidProperties(args, standardProperties.concat('placeHolder')); this.value = args.value || null; this.placeHolder = args.placeHolder || null; break; case 'Boolean': unusedProperties = getInvalidProperties(args, standardProperties); if (args.value === false) this.value = false; else this.value = args.value || null; break; case 'Number': unusedProperties = getInvalidProperties(args, standardProperties.concat('placeHolder')); if (args.value === 0) this.value = 0; else this.value = args.value || null; this.placeHolder = args.placeHolder || null; break; case 'Model': unusedProperties = getInvalidProperties(args, standardProperties); this.value = args.value || null; if (this.value instanceof Attribute.ModelID) this.modelType = this.value.modelType; break; case 'Group': unusedProperties = getInvalidProperties(args, standardProperties); this.value = args.value || null; break; case 'Table': unusedProperties = getInvalidProperties(args, standardProperties.concat('group')); this.value = args.value || null; this.group = args.group || null; break; case 'Object': unusedProperties = getInvalidProperties(args, standardProperties); this.value = args.value || null; break; default: break; } var errorList = this.getObjectStateErrors(); // before leaving make sure valid Attribute for (var i = 0; i < unusedProperties.length; i++) errorList.push('invalid property: ' + unusedProperties[i]); if (errorList.length > 1) throw new Error('error creating Attribute: multiple errors'); if (errorList.length) throw new Error('error creating Attribute: ' + errorList[0]); // Validations done this._eventListeners = []; this._errorConditions = {}; } /** * Additional Constructors */ Attribute.ModelID = function (model) { if (false === (this instanceof Attribute.ModelID)) throw new Error('new operator required'); if (false === (model instanceof Model)) throw new Error('must be constructed with Model'); var shorty = model.getShortName(); if (shorty) this.name = shorty; this.value = model.get('id'); this.constructorFunction = model.constructor; this.modelType = model.modelType; }; Attribute.ModelID.prototype.toString = function () { if (this.name) return this.modelType + ' ' + this.name; else return this.modelType + ' ' + this.value; //if (typeof this.value == 'string') // return 'ModelID(' + this.modelType + ':\'' + this.value + '\')'; //else // return this.modelType + ' ' + this.value; }; /** * Methods */ Attribute.prototype.toString = function () { return this.name === null ? 'new Attribute' : 'Attribute: ' + this.name + ' = ' + this.value; }; Attribute.prototype.onEvent = function (events, callback) { if (!(events instanceof Array)) { if (typeof events != 'string') throw new Error('subscription string or array required'); events = [events]; // coerce to array } if (typeof callback != 'function') throw new Error('callback is required'); // Check known Events for (var i in events) { if (events.hasOwnProperty(i)) if (events[i] != '*') if (!contains(['StateChange', 'Validate'], events[i])) throw new Error('Unknown command event: ' + events[i]); } // All good add to chain this._eventListeners.push({events: events, callback: callback}); return this; }; Attribute.prototype.offEvent = function () { this._eventListeners = []; }; Attribute.prototype._emitEvent = function (event) { var i; for (i in this._eventListeners) { if (this._eventListeners.hasOwnProperty(i)) { var subscriber = this._eventListeners[i]; if ((subscriber.events.length && subscriber.events[0] === '*') || contains(subscriber.events, event)) { subscriber.callback.call(this, event); } } } }; Attribute.prototype.get = function () { return this.value; }; Attribute.prototype.set = function (newValue) { switch (this.type) { case 'Model': if (newValue instanceof Attribute.ModelID) this.value = newValue; else { throw new Error('set error: value must be Attribute.ModelID'); } break; default: this.value = newValue; } this._emitEvent('StateChange'); return this.value; }; Attribute.prototype.coerce = function (value) { var newValue = value; var temp; switch (this.type) { case 'String': if (typeof newValue == 'undefined') return ''; if (typeof newValue == 'boolean' && !newValue) return 'false'; if (!newValue) return ''; newValue = value.toString(); if (newValue.length > this.size) return newValue.substring(0, this.size); return newValue; case 'Number': if (typeof newValue == 'undefined') return 0; if (!newValue) return 0; if (typeof newValue == 'string') { newValue = newValue.replace(/^\s+|\s+$/g, ''); // trim temp = newValue.split(' '); newValue = temp.length ? temp[0] : ''; newValue = Number(newValue.replace(/[^/0-9\ \.]+/g, "")); } else { newValue = Number(newValue); } if (!newValue) return 0; return newValue; case 'Boolean': if (typeof newValue == 'undefined') return false; if (typeof newValue == 'string') { newValue = newValue.toUpperCase(); if (newValue === 'Y' || newValue === 'YES' || newValue === 'T' || newValue === 'TRUE' || newValue === '1') return true; return false; } return (newValue ? true : false); // truthy all we need case 'Date': if (typeof newValue == 'string') { if (newValue.split('/').length == 2) newValue = newValue + '/' + new Date().getFullYear(); } return new Date(newValue); } throw(Error('coerce cannot determine appropriate value')); }; Attribute.prototype.getObjectStateErrors = function () { var i; this.validationErrors = []; if (!this.name) this.validationErrors.push('name required'); if (!contains(['ID', 'String', 'Date', 'Boolean', 'Number', 'Model', 'Group', 'Table', 'Object'], this.type)) this.validationErrors.push('Invalid type: ' + this.type); switch (this.type) { case 'ID': break; case 'String': if (typeof this.size != 'number') this.validationErrors.push('size must be a number from 1 to 255'); if (this.size < 1 || this.size > 255) this.validationErrors.push('size must be a number from 1 to 255'); if (!(this.value === null || typeof this.value === 'string')) this.validationErrors.push('value must be null or a String'); break; case 'Date': if (!(this.value === null || this.value instanceof Date)) this.validationErrors.push('value must be null or a Date'); break; case 'Boolean': if (!(this.value === null || typeof this.value == 'boolean')) this.validationErrors.push('value must be null or a Boolean'); break; case 'Number': if (!(this.value === null || typeof this.value == 'number')) this.validationErrors.push('value must be null or a Number'); break; case 'Model': if (!(this.value instanceof Attribute.ModelID)) this.validationErrors.push('value must be Attribute.ModelID'); break; case 'Group': if (this.value === null || this.value instanceof Array) { for (i in this.value) { if (this.value.hasOwnProperty(i)) { if (!(this.value[i] instanceof Attribute)) this.validationErrors.push('each element in group must be instance of Attribute'); if (this.value[i].getObjectStateErrors().length) this.validationErrors.push('group contains invalid members'); } } } else { this.validationErrors.push('value must be null or an array'); } break; case 'Table': if (!(this.group instanceof Attribute)) { this.validationErrors.push('group property required'); } else { if (this.group.value instanceof Array) { if (this.group.value.length < 1) { this.validationErrors.push('group property value must contain at least one Attribute'); } else { for (i in this.group.value) { if (this.group.value.hasOwnProperty(i)) { if (!(this.group.value[i] instanceof Attribute)) this.validationErrors.push('each element in group must be instance of Attribute'); if (this.group.value[i].getObjectStateErrors().length) this.validationErrors.push('group contains invalid members'); } } } } else { this.validationErrors.push('group.value must be an array'); } } break; default: break; } var validationRuleBadProps = getInvalidProperties(this.validationRule, ['required', 'range', 'isOneOf', 'isValidModel']); if (validationRuleBadProps.length) this.validationErrors.push('invalid validationRule: ' + validationRuleBadProps); this.validationMessage = this.validationErrors.length > 0 ? this.validationErrors[0] : ''; return this.validationErrors; }; Attribute.prototype.validate = function (callback) { if (typeof callback != 'function') throw new Error('callback is required'); // First check object state this.getObjectStateErrors(); this._emitEvent('Validate'); var e; for (e in this._errorConditions) { if (this._errorConditions.hasOwnProperty(e)) { this.validationErrors.push(this._errorConditions[e]); } } // Check validation rules for attribute if (this.validationRule.required && !this.value) { if (this.type == 'Number') { if (this.value !== 0) this.validationErrors.push(this.label + ' required'); } else if (this.type == 'Boolean') { if (this.value !== false) this.validationErrors.push(this.label + ' required'); } else { this.validationErrors.push(this.label + ' required'); } } if (this.validationRule.range) { if (!(this.validationRule.range instanceof Array)) { this.validationRule.range = [this.validationRule.range]; // coerce to array } if (this.validationRule.range[0] || this.validationRule.range[0] === 0) { if (this.value < this.validationRule.range[0]) this.validationErrors.push(this.label + ' must be at least ' + this.validationRule.range[0]); } if (this.validationRule.range[1] || this.validationRule.range[1] === 0) { if (this.value > this.validationRule.range[1]) this.validationErrors.push(this.label + ' must be no more than ' + this.validationRule.range[1]); } } if (this.validationRule.isOneOf) { if (!(this.validationRule.isOneOf instanceof Array)) { this.validationRule.isOneOf = [this.validationRule.isOneOf]; // coerce to array } if (this.validationRule.isOneOf.indexOf(this.value) == -1) this.validationErrors.push(this.label + ' invalid'); } // All done... this.validationMessage = this.validationErrors.length > 0 ? this.validationErrors[0] : ''; this._emitEvent('StateChange'); callback.call(this); }; Attribute.prototype.setError = function (condition, description) { condition = condition || ''; description = description || ''; if (!condition) throw new Error('condition required'); if (!description) throw new Error('description required'); this._errorConditions[condition] = description; }; Attribute.prototype.clearError = function (condition) { condition = condition || ''; if (!condition) throw new Error('condition required'); delete this._errorConditions[condition]; }; /** * Simple functions */ Attribute.getTypes = function () { return ['ID', 'String', 'Date', 'Boolean', 'Number', 'Model', 'Group', 'Table', 'Object'].slice(0); // copy array }; Attribute.getEvents = function () { return ['StateChange', 'Validate'].slice(0); // copy array }; /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/tgi-core-command.source.js */ /** * Command Constructor */ function Command(args) { if (false === (this instanceof Command)) throw new Error('new operator required'); if (typeof args == 'function') { // shorthand for function command args = {type: 'Function', contents: args}; } if (args instanceof Procedure) { // shorthand for Procedure command args = {type: 'Procedure', contents: args}; } args = args || {}; var i; var unusedProperties = getInvalidProperties(args, ['name', 'description', 'type', 'contents', 'scope', 'timeout', 'theme', 'icon', 'bucket', 'presentationMode','location','images']); var errorList = []; for (i = 0; i < unusedProperties.length; i++) errorList.push('invalid property: ' + unusedProperties[i]); if (errorList.length > 1) throw new Error('error creating Command: multiple errors'); if (errorList.length) throw new Error('error creating Command: ' + errorList[0]); for (i in args) this[i] = args[i]; this.name = this.name || "a command"; // name is optional if ('string' != typeof this.name) throw new Error('name must be string'); if ('undefined' == typeof this.description) this.description = this.name + ' Command'; if ('undefined' == typeof this.type) this.type = 'Stub'; if (!contains(Command.getTypes(), this.type)) throw new Error('Invalid command type: ' + this.type); switch (this.type) { case 'Stub': break; case 'Menu': if (!(this.contents instanceof Array)) throw new Error('contents must be array of menu items'); if (!this.contents.length) throw new Error('contents must be array of menu items'); for (i in this.contents) { if (this.contents.hasOwnProperty(i)) if (typeof this.contents[i] != 'string' && !(this.contents[i] instanceof Command)) throw new Error('contents must be array of menu items'); } break; case 'Presentation': if (!(this.contents instanceof Presentation)) throw new Error('contents must be a Presentation'); this.presentationMode = this.presentationMode || 'View'; if (!contains(Command.getPresentationModes(), this.presentationMode)) throw new Error('Invalid presentationMode: ' + this.presentationMode); //['View', 'Edit', 'List'] break; case 'Function': if (typeof this.contents != 'function') throw new Error('contents must be a Function'); break; case 'Procedure': if (!(this.contents instanceof Procedure)) throw new Error('contents must be a Procedure'); break; default: throw new TypeError(); } if ('undefined' != typeof this.scope) if (!((this.scope instanceof Model) || (this.scope instanceof List))) throw new Error('optional scope property must be Model or List'); if ('undefined' != typeof this.timeout) if (typeof this.timeout != 'number') throw new Error('timeout must be a Number'); if ('undefined' != typeof this.timeout) if (typeof this.timeout != 'number') throw new Error('timeout must be a Number'); if ('undefined' != typeof this.theme) { if ('string' != typeof this.theme) throw new Error('invalid theme'); if (!contains(['default', 'primary', 'success', 'info', 'warning', 'danger', 'link'], this.theme)) throw new Error('invalid theme'); } if ('undefined' != typeof this.icon) { if ('string' != typeof this.icon) throw new Error('invalid icon'); if (!contains(['fa', 'glyphicon'], this.icon.split('-')[0]) || !this.icon.split('-')[1]) throw new Error('invalid icon'); } // Validations done this._eventListeners = []; } /** * Methods */ Command.prototype.toString = function () { return this.type + ' Command: ' + this.name; }; Command.prototype.onEvent = function (events, callback) { if (!(events instanceof Array)) { if (typeof events != 'string') throw new Error('subscription string or array required'); events = [events]; // coerce to array } if (typeof callback != 'function') throw new Error('callback is required'); // Check known Events for (var i in events) { if (events.hasOwnProperty(i)) if (events[i] != '*') if (!contains(['BeforeExecute', 'AfterExecute', 'Error', 'Aborted', 'Completed'], events[i])) throw new Error('Unknown command event: ' + events[i]); } // All good add to chain this._eventListeners.push({events: events, callback: callback}); }; Command.prototype._emitEvent = function (event, obj) { var i; for (i in this._eventListeners) { if (this._eventListeners.hasOwnProperty(i)) { var subscriber = this._eventListeners[i]; if ((subscriber.events.length && subscriber.events[0] === '*') || contains(subscriber.events, event)) { subscriber.callback.call(this, event, obj); } } } //if (event == 'Completed') // if command complete release listeners // this._eventListeners = []; }; Command.prototype.execute = function (context) { var command = this; var args = arguments; if (!command.type) throw new Error('command not implemented'); if (!contains(['Function', 'Procedure', 'Menu', 'Presentation'], command.type)) throw new Error('command type ' + command.type + ' not implemented'); var errors; switch (command.type) { case 'Presentation': if (!(command.contents instanceof Presentation)) throw new Error('contents must be a Presentation'); errors = command.contents.getObjectStateErrors(); if (errors.length) { if (errors.length > 1) throw new Error('error executing Presentation: multiple errors'); else throw new Error('error executing Presentation: ' + errors[0]); } if (!(context instanceof Interface)) throw new Error('interface param required'); break; } command._emitEvent('BeforeExecute'); try { switch (command.type) { case 'Function': setTimeout(callFunc, 0); break; case 'Procedure': setTimeout(procedureExecuteInit, 0); break; case 'Menu': context.render(command, 'View'); break; case 'Presentation': if (command.contents.preRenderCallback) { command.contents.preRenderCallback(command, function () { context.render(command); }); } else { context.render(command); } break; } } catch (e) { command.error = e; command._emitEvent('Error', e); command._emitEvent('Completed'); command.status = -1; } command._emitEvent('AfterExecute'); function callFunc() { command.status = 0; try { command.contents.apply(command, args); // give function this context to command object (command) } catch (e) { command.error = e; command._emitEvent('Error', e); command._emitEvent('Completed'); command.status = -1; } } function procedureExecuteInit() { command.status = 0; var tasks = command.contents.tasks || []; for (var t = 0; t < tasks.length; t++) { // shorthand for function command gets coerced into longhand if (typeof tasks[t] == 'function') { var theFunc = tasks[t]; tasks[t] = {requires: [-1], command: new Command({type: 'Function', contents: theFunc})}; } // Initialize if not done if (!tasks[t].command._parentProcedure) { tasks[t].command._taskIndex = t; tasks[t].command._parentProcedure = command; tasks[t].command.onEvent('*', ProcedureEvents); } tasks[t].command.status = undefined; } procedureExecute(); } function procedureExecute() { var tasks = command.contents.tasks || []; for (var t = 0; t < tasks.length; t++) { // Execute if it is time var canExecute = true; if (typeof (tasks[t].command.status) == 'undefined') { for (var r in tasks[t].requires) { if (typeof tasks[t].requires[r] == 'string') { // label of task needed to complete for (var l = 0; l < tasks.length; l++) { if (tasks[l].label == tasks[t].requires[r]) if (!tasks[l].command.status || tasks[l].command.status <= 0) { canExecute = false; } } } if (typeof tasks[t].requires[r] == 'number') { if (tasks[t].requires[r] == -1) { // previous task needed to complete? if (t != '0') { // first one always runs if (!tasks[t - 1].command.status || tasks[t - 1].command.status <= 0) { canExecute = false; } } } else { var rq = tasks[t].requires[r]; if (!tasks[rq].command.status || tasks[rq].command.status <= 0) { canExecute = false; } } } } if (canExecute) { tasks[t].command.execute(); } } } } function ProcedureEvents(event, obj) { var tasks = command.contents.tasks; var allTasksDone = true; // until proved wrong ... switch (event) { case 'Error': command._emitEvent('Error', obj); break; case 'Aborted': command.abort(); break; case 'Completed': for (var t in tasks) { if (tasks.hasOwnProperty(t)) { if (!tasks[t].command.status || tasks[t].command.status === 0) { allTasksDone = false; } } } if (allTasksDone) command.complete(); // todo when all run else procedureExecute(); break; } } }; Command.prototype.abort = function () { this._emitEvent('Aborted'); this.status = -1; this._emitEvent('Completed'); }; Command.prototype.complete = function () { this.status = 1; this._emitEvent('Completed'); }; Command.prototype.restart = function () { this.status = undefined; this._emitEvent('Restarted'); this.execute(); }; /** * Simple functions */ Command.getTypes = function () { return ['Stub', 'Menu', 'Presentation', 'Function', 'Procedure'].slice(0); // copy array }; Command.getEvents = function () { return ['BeforeExecute', 'AfterExecute', 'Error', 'Aborted', 'Completed'].slice(0); // copy array }; Command.getPresentationModes = function () { return ['View', 'Edit', 'List'].slice(0); // copy array }; /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/tgi-core-delta.source.js */ /** * Constructor */ function Delta(modelID) { if (false === (this instanceof Delta)) throw new Error('new operator required'); if (false === (modelID instanceof Attribute.ModelID)) throw new Error('Attribute.ModelID required in constructor'); this.dateCreated = new Date(); this.modelID = modelID; this.attributeValues = {}; } /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/tgi-core-interface.source.js */ /** * Constructor */ function Interface(args) { if (false === (this instanceof Interface)) throw new Error('new operator required'); args = args || {}; args.name = args.name || '(unnamed)'; args.description = args.description || 'a Interface'; args.vendor = args.vendor || null; var i; var unusedProperties = getInvalidProperties(args, ['name', 'description', 'vendor']); var errorList = []; for (i = 0; i < unusedProperties.length; i++) errorList.push('invalid property: ' + unusedProperties[i]); if (errorList.length > 1) throw new Error('error creating Interface: multiple errors'); if (errorList.length) throw new Error('error creating Interface: ' + errorList[0]); // default state this.startcallback = null; this.stopcallback = null; this.mocks = []; this.mockPending = false; // args ok, now copy to object for (i in args) this[i] = args[i]; } /** * Methods */ Interface.prototype.toString = function () { return this.description; }; Interface.prototype.canMock = function () { return true; }; Interface.prototype.doMock = function () { var callback, result; // If no more elements then we are done this.mockPending = false; if (this.mocks.length < 1) return; // Get oldest ele and pass to callback if it is set var thisMock = this.mocks.shift(); if (thisMock.type == 'ok') { if (this.okcallback) { callback = this.okcallback; delete this.okcallback; callback(); } else { this.okPending = true; } return; } if (thisMock.type == 'yes' || thisMock.type == 'no' || thisMock.type == 'cancel') { switch (thisMock.type) { case 'yes': result = true; break; case 'no': result = false; break; case 'cancel': result = undefined; break; } if (this.yesnocallback) { callback = this.yesnocallback; delete this.yesnocallback; callback(result); } else { this.yesnoPending = true; this.yesnoResponse = result; } return; } if (thisMock.type == 'ask') { if (this.askcallback) { callback = this.askcallback; delete this.askcallback; callback(thisMock.value); } else { this.askPending = true; this.askResponse = thisMock.value; } return; } if (thisMock.type == 'choose') { if (this.choosecallback) { callback = this.choosecallback; delete this.choosecallback; callback(Interface.firstMatch(thisMock.value, this.chooseChoices)); } else { this.choosePending = true; this.chooseResponse = thisMock.value; } return; } this.dispatch(thisMock); // Invoke for next element (delayed execution) this.mockPending = true; var self = this; setTimeout(function () { self.doMock(); }, 0); }; Interface.prototype.mockRequest = function (args) { if (!(args instanceof Array || args instanceof Request)) throw new Error('missing request parameter'); if (!(args instanceof Array)) args = [args]; // coerce to array var i; for (i = 0; i < args.length; i++) { if (false === (args[i] instanceof Request)) throw new Error('invalid request parameter'); } // All good stack them for (i = 0; i < args.length; i++) { this.mocks.push(args[i]); } // If mock is not pending then start it if (!this.mockPending) { this.doMock(); } }; Interface.prototype.start = function (application, presentation, callback) { if (!(application instanceof Application)) throw new Error('Application required'); if (!(presentation instanceof Presentation)) throw new Error('presentation required'); if (typeof callback != 'function') throw new Error('callback required'); this.application = application; this.presentation = presentation; this.startcallback = callback; }; Interface.prototype.stop = function (callback) { if (typeof callback != 'function') throw new Error('callback required'); }; Interface.prototype.dispatch = function (request, response) { if (false === (request instanceof Request)) throw new Error('Request required'); if (response && typeof response != 'function') throw new Error('response callback is not a function'); if (!this.application || !this.application.dispatch(request)) { if (this.startcallback) { this.startcallback(request); } } }; Interface.prototype.notify = function (message) { if (false === (message instanceof Message)) throw new Error('Message required'); }; Interface.prototype.render = function (command, callback) { if (false === (command instanceof Command)) throw new Error('Command object required'); //if (!contains(Command.getPresentationModes(), presentationMode)) throw new Error('Invalid presentationMode: ' + presentationMode); //if (callback && typeof callback != 'function') throw new Error('optional second argument must a commandRequest callback function'); }; Interface.prototype.info = function (text) { if (!text || typeof text !== 'string') throw new Error('text required'); }; Interface.prototype.done = function (text) { if (!text || typeof text !== 'string') throw new Error('text required'); }; Interface.prototype.warn = function (text) { if (!text || typeof text !== 'string') throw new Error('text required'); }; Interface.prototype.err = function (text) { if (!text || typeof text !== 'string') throw new Error('text required'); }; Interface.prototype.ok = function (prompt, callback) { if (!prompt || typeof prompt !== 'string') throw new Error('prompt required'); if (typeof callback != 'function') throw new Error('callback required'); if (this.okPending) { delete this.okPending; callback(); } else { this.okcallback = callback; } }; Interface.prototype.yesno = function (prompt, callback) { if (!prompt || typeof prompt !== 'string') throw new Error('prompt required'); if (typeof callback != 'function') throw new Error('callback required'); if (this.yesnoPending) { delete this.yesnoPending; callback(this.yesnoResponse); } else { this.yesnocallback = callback; } }; Interface.prototype.ask = function (prompt, attribute, callback) { if (!prompt || typeof prompt !== 'string') throw new Error('prompt required'); if (false === (attribute instanceof Attribute)) throw new Error('attribute or callback expected'); if (typeof callback != 'function') throw new Error('callback required'); if (this.askPending) { delete this.askPending; callback(this.askResponse); } else { this.askcallback = callback; } }; Interface.prototype.choose = function (prompt, choices, callback) { if (!prompt || typeof prompt !== 'string') throw new Error('prompt required'); if (false === (choices instanceof Array)) throw new Error('choices array required'); if (!choices.length) throw new Error('choices array empty'); if (typeof callback != 'function') throw new Error('callback required'); if (this.choosePending) { delete this.choosePending; callback(Interface.firstMatch(this.chooseResponse, choices)); } else { this.choosecallback = callback; this.chooseChoices = choices; } }; /** * Helper Functions */ Interface.firstMatch = function (s, a) { // find first partial match with s in array a if (undefined === s) return undefined; for (var i = 0; i < a.length; i++) { var obj = a[i].toLowerCase(); if (left(obj, s.length) == s.toLowerCase()) return i; } return undefined; }; /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/tgi-core-list.source.js */ // Constructor var List = function (source) { if (false === (this instanceof List)) throw new Error('new operator required'); if (source instanceof Model) { this.model = source; } else if (source instanceof View) { this.view = source; this.model = this.view.primaryModel; } else { throw new Error('argument required: model'); } this.attributes = source.attributes; this._items = []; this._itemIndex = -1; }; List.prototype.length = function () { return this._items.length; }; List.prototype.clear = function () { this._items = []; this._itemIndex = -1; return this; }; List.prototype.get = function (attribute) { if (this._items.length < 1) throw new Error('list is empty'); for (var i = 0; i < this.attributes.length; i++) { var curName = this.attributes[i].name.toUpperCase(); var wantedName = attribute.toUpperCase(); var splitName = wantedName.split('.'); //console.log('curName : ' + curName); //console.log('wantedName : ' + wantedName + ' size ' + splitName.length); var matches = (curName == wantedName); if (splitName.length == 2) { wantedName = splitName[1]; var wantedModel = splitName[0]; var curModel = this.attributes[i].model.modelType.toUpperCase(); matches = (curName == wantedName) && (wantedModel==curModel); } if (matches) { if (this.attributes[i].type == 'Date' && !(this._items[this._itemIndex][i] instanceof Date)) { if (this._items[this._itemIndex][i] === null || this._items[this._itemIndex][i] === undefined) return null; else return new Date(this._items[this._itemIndex][i]); // todo problem with stores not keeping date type (mongo or host) kludge fix for now } else { return this._items[this._itemIndex][i]; } } } }; List.prototype.set = function (attribute, value) { if (this._items.length < 1) throw new Error('list is empty'); for (var i = 0; i < this.attributes.length; i++) { var curName = this.attributes[i].name.toUpperCase(); var wantedName = attribute.toUpperCase(); var splitName = wantedName.split('.'); //console.log('curName : ' + curName); //console.log('wantedName : ' + wantedName + ' size ' + splitName.length); var matches = (curName == wantedName); if (splitName.length == 2) { wantedName = splitName[1]; var wantedModel = splitName[0]; var curModel = this.attributes[i].model.modelType.toUpperCase(); matches = (curName == wantedName) && (wantedModel==curModel); } if (matches) { this._items[this._itemIndex][i] = value; return; } } throw new Error('attribute not valid for list model'); }; List.prototype.addItem = function (item) { var i; var values = []; if (item) { for (i in item.attributes) { values.push(item.attributes[i].value); } } else { for (i in this.attributes) { values.push(undefined); } } this._items.push(values); this._itemIndex = this._items.length - 1; return this; }; List.prototype.removeItem = function () { this._items.splice(this._itemIndex, 1); this._itemIndex--; return this; }; List.prototype.findItemByID = function (id) { var gotMore = this.moveFirst(); while (gotMore) { if (id == this._items[this._itemIndex][0]) return true; gotMore = this.moveNext(); } return false; }; List.prototype.indexedItem = function (index) { if (this._items.length < 1) return false; if (index < 0) return false; if (index >= this._items.length) return false; this._itemIndex = index; return true; }; List.prototype.moveNext = function () { if (this._items.length < 1) return false; return this.indexedItem(this._itemIndex + 1); }; List.prototype.movePrevious = function () { if (this._items.length < 1) return false; return this.indexedItem(this._itemIndex - 1); }; List.prototype.moveFirst = function () { if (this._items.length < 1) return false; return this.indexedItem(0); }; List.prototype.moveLast = function () { if (this._items.length < 1) return false; return this.indexedItem(this._items.length - 1); }; List.prototype.sort = function (key) { var i = 0; var keyvalue; for (var keyName in key) { if (!keyvalue) keyvalue = keyName; } if (!keyvalue) throw new Error('sort order required'); var ascendingSort = (key[keyvalue] == 1); while (i < this.attributes.length && this.attributes[i].name != keyvalue) i++; this._items.sort(function (a, b) { if (ascendingSort) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } else { if (a[i] > b[i]) return -1; if (a[i] < b[i]) return 1; } return 0; }); }; /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/core/tgi-core-message.source.js */ /** * Constructor */ function Message(type, contents) { if (false === (this instanceof Message)) throw new Error('new operator required'); if ('undefined' == typeof type) throw new Error('message type required'); if (!contains(Message.getTypes(), type)) throw new Error('Invalid message type: ' + type); this.type = type; this.contents = contents; } /** * Methods */ Message.prototype.toString = function () { switch (this.type) { case 'Null': return this.type + ' Message'; default: return this.type + ' Message: ' + this.contents; } }; /** * Simple functions */ Message.getTypes = function () { return [ 'Null', 'Connected', 'Error', 'Sent', 'Ping', 'PutModel', 'PutModelAck', 'GetModel', 'GetModelAck', 'DeleteModel', 'DeleteModelAck', 'GetList', 'GetListAck' ].slice(0); // copy array }; /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/tgi-core-model.source.js */ /** * Model Constructor */ var Model = function (args) { var i; if (false === (this instanceof Model)) throw new Error('new operator required'); this.attributes = [new Attribute('id', 'ID')]; args = args || {}; this.modelType = args.modelType || "Model"; if (args.attributes) { for (i in args.attributes) { if (args.attributes.hasOwnProperty(i)) this.attributes.push(args.attributes[i]); } } var unusedProperties = getInvalidProperties(args, ['modelType', 'attributes']); var errorList = this.getObjectStateErrors(); // before leaving make sure valid Model for (i = 0; i < unusedProperties.length; i++) errorList.push('invalid property: ' + unusedProperties[i]); if (errorList.length > 1) throw new Error('error creating Model: multiple errors'); if (errorList.length) throw new Error('error creating Model: ' + errorList[0]); // Validations done this._eventListeners = []; this._errorConditions = {}; //for (i = 0; i < this.attributes.length; i++) { // this.attributes[i].model = this; //} }; Model._ModelConstructor = {}; /** * Methods */ Model.prototype.toString = function () { return "a " + this.modelType; }; Model.prototype.copy = function (sourceModel) { for (var i = 0; i < this.attributes.length; i++) { //if (args.attributes.hasOwnProperty(i)) this.attributes[i].value = sourceModel.attributes[i].value; } }; Model.prototype.getObjectStateErrors = function () { this.validationErrors = []; // check attributes if (!(this.attributes instanceof Array)) { this.validationErrors.push('attributes must be Array'); } else { if (this.attributes.length < 1) { this.validationErrors.push('attributes must not be empty'); } else { for (var i = 0; i < this.attributes.length; i++) { if (i === 0 && (!(this.attributes[i] instanceof Attribute) || this.attributes[i].type != "ID")) this.validationErrors.push('first attribute must be ID'); if (!(this.attributes[i] instanceof Attribute)) this.validationErrors.push('attribute must be Attribute'); } } } // check tags if (this.tags !== undefined && !(this.tags instanceof Array)) { this.validationErrors.push('tags must be Array or null'); } return this.validationErrors; }; Model.prototype.attribute = function (attributeName) { for (var i = 0; i < this.attributes.length; i++) { if (this.attributes[i].name.toUpperCase() == attributeName.toUpperCase()) return this.attributes[i]; } throw new Error('attribute not found in model: ' + attributeName); }; Model.prototype.get = function (attribute) { for (var i = 0; i < this.attributes.length; i++) { if (this.attributes[i].name.toUpperCase() == attribute.toUpperCase()) return this.attributes[i].get(); } }; Model.prototype.getShortName = function () { for (var i = 0; i < this.attributes.length; i++) { if (this.attributes[i].type == 'String') return this.attributes[i].get(); } return ''; }; Model.prototype.getLongName = function () { return this.getShortName(); }; Model.prototype.getAttributeType = function (attribute) { for (var i = 0; i < this.attributes.length; i++) { if (this.attributes[i].name.toUpperCase() == attribute.toUpperCase()) return this.attributes[i].type; } }; Model.prototype.set = function (attribute, value) { for (var i = 0; i < this.attributes.length; i++) { if (this.attributes[i].name.toUpperCase() == attribute.toUpperCase()) { this.attributes[i].set(value); this._emitEvent('StateChange'); return; } } throw new Error('attribute not valid for model'); }; Model.prototype.validate = function (callback) { var model = this; var i, e; var validationsPending = 0; // track callbacks sent if (typeof callback != 'function') throw new Error('callback is required'); // First check object state model.getObjectStateErrors(); for (e in model._errorConditions) { if (model._errorConditions.hasOwnProperty(e)) { model.validationErrors.push(model._errorConditions[e]); } } // If model wrong here abort attribute tests if (model.validationErrors.length) { model.validationMessage = model.validationErrors.length > 0 ? model.validationErrors[0] : ''; model._emitEvent('StateChange'); callback.call(model); return; } // Now check each attribute /* jshint ignore:start */ // todo Don't make functions within a loop. for (i = 0; i < model.attributes.length; i++) { validationsPending++; (function (curAttribute) { setTimeout(function () { curAttribute.validate(function () { if (curAttribute.validationErrors.length) { model.validationErrors.push('bush'); } // done with this one - see if done with all if (--validationsPending === 0) { /** Final test is here ... **/ // If no errors in attributes validate model if (!model.validationErrors.length) model._emitEvent('Validate'); // Finally done here! model.validationMessage = model.validationErrors.length > 0 ? model.validationErrors[0] : ''; model._emitEvent('StateChange'); callback.call(model); } }); }, 0); }(model.attributes[i])); } /* jshint ignore:end */ // // All done... // this.validationMessage = this.validationErrors.length > 0 ? this.validationErrors[0] : ''; // this._emitEvent('StateChange'); // callback.call(this); }; Model.prototype.onEvent = function (events, callback) { if (!(events instanceof Array)) { if (typeof events != 'string') throw new Error('subscription string or array required'); events = [events]; // coerce to array } if (typeof callback != 'function') throw new Error('callback is required'); // Check known Events for (var i in events) { if (events.hasOwnProperty(i)) if (events[i] != '*') if (!contains(['StateChange', 'Validate'], events[i])) throw new Error('Unknown command event: ' + events[i]); } // All good add to chain this._eventListeners.push({events: events, callback: callback}); return this; }; Model.prototype._emitEvent = function (event, meta) { // todo meta is app defined - no test for it var i; for (i in this._eventListeners) { if (this._eventListeners.hasOwnProperty(i)) { var subscriber = this._eventListeners[i]; if ((subscriber.events.length && subscriber.events[0] === '*') || contains(subscriber.events, event)) { subscriber.callback.call(this, event, meta); } } } }; Model.prototype.setError = function (condition, description) { condition = condition || ''; description = description || ''; if (!condition) throw new Error('condition required'); if (!description) throw new Error('description required'); this._errorConditions[condition] = description; }; Model.prototype.clearError = function (condition) { condition = condition || ''; if (!condition) throw new Error('condition required'); delete this._errorConditions[condition]; }; /**--------------------------------------------------------------------------------------------------------------------- * tgi-core/lib/core/tgi-core-procedure.source.js */ /** * Model Constructor */ var Procedure = function (args) { if (false === (this instanceof Procedure)) throw new Error('new operator required'); if (args instanceof Array) { // shorthand for Procedure command args = {tasks: args}; } args = args || {}; var i; var unusedProperties = getInvalidProperties(args, ['tasks', 'tasksNeeded', 'tasksCompleted']); var errorList = []; for (i = 0; i < unusedProperties.length; i++) errorList.push('invalid property: ' + unusedProperties[i]); if (errorList.length > 1) throw new Error('error creating Procedure: multiple errors'); if (errorList.length) throw new Error('error creating Procedure: ' + errorList[0]); // args ok, now copy to object and check for errors for (i in args) if (args.hasOwnProperty(i)) this[i] = args[i]; errorList = this.getObjectStateErrors(); // before leaving make sure valid Attribute if (errorList) { if (errorList.length > 1) throw new Error('error creating Procedure: multiple errors'); if (errorList.length) throw new Error('error creating Procedure: ' + errorList[0]); } }; Procedure.prototype.getObjectStateErrors = function () { var i, j, k; var unusedProperties; if (this.tasks && !(this.tasks instanceof Array)) return ['tasks is not an array']; var errorList = []; for (i in this.tasks) { if (this.tasks.hasOwnProperty(i)) { var task = this.tasks[i]; unusedProperties = getInvalidProperties(task, ['label', 'command', 'requires', 'timeout']); for (j = 0; j < unusedProperties.length; j++) errorList.push('invalid task[' + i + '] property: ' + unusedProperties[j]); if (typeof task.label != 'undefined' && typeof task.label != 'string') errorList.push('task[' + i + '].label must be string'); if (typeof task.command != 'undefined' && !(task.command instanceof Command)) errorList.push('task[' + i + '].command must be a Command object'); // make sure requires valid if specified if (typeof task.requires == 'undefined') task.requires = -1; // default to if (!(task.requires instanceof Array)) task.requires = [task.requires]; // coerce to array for (j in task.requires) { if (task.requires.hasOwnProperty(j) && task.requires[j] !== null) switch (typeof task.requires[j]) { case 'string': // make sure label exists var gotLabel = false; for (k=0; !gotLabel && k<this.tasks.length; k++ ) if (task.requires[j] == this.tasks[k].label) gotLabel = true; if (!gotLabel) throw new Error('missing label: ' + task.requires[j]); break; case 'number': if (task.requires[j] >= this.tasks.length) throw new Error('missing task #' + task.requires[j] + ' for requires in task