tgi-store-mongodb
Version:
1,382 lines (1,363 loc) • 121 kB
JavaScript
/**---------------------------------------------------------------------------------------------------------------------
* 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