2014-10-23 05:41:00 +00:00
|
|
|
/* jslint node: true */
|
|
|
|
'use strict';
|
|
|
|
|
2015-03-30 03:47:48 +00:00
|
|
|
// ENiGMA½
|
2014-10-23 05:41:00 +00:00
|
|
|
var MCIViewFactory = require('./mci_view_factory.js').MCIViewFactory;
|
2015-03-28 00:02:00 +00:00
|
|
|
var menuUtil = require('./menu_util.js');
|
2015-03-30 03:47:48 +00:00
|
|
|
var Log = require('./logger.js').log;
|
2015-04-19 08:13:13 +00:00
|
|
|
var Config = require('./config.js').config;
|
2015-04-05 07:15:04 +00:00
|
|
|
var asset = require('./asset.js');
|
2015-03-28 00:02:00 +00:00
|
|
|
|
2015-03-30 03:47:48 +00:00
|
|
|
var events = require('events');
|
|
|
|
var util = require('util');
|
|
|
|
var assert = require('assert');
|
2015-03-28 00:02:00 +00:00
|
|
|
var async = require('async');
|
2015-04-02 04:13:29 +00:00
|
|
|
var _ = require('lodash');
|
2015-04-19 08:13:13 +00:00
|
|
|
var paths = require('path');
|
2014-10-23 05:41:00 +00:00
|
|
|
|
|
|
|
exports.ViewController = ViewController;
|
|
|
|
|
2015-04-19 08:13:13 +00:00
|
|
|
var MCI_REGEXP = /([A-Z]{2})([0-9]{1,2})/;
|
|
|
|
|
2015-04-14 06:19:14 +00:00
|
|
|
function ViewController(options) {
|
|
|
|
assert(_.isObject(options));
|
|
|
|
assert(_.isObject(options.client));
|
|
|
|
|
2014-10-23 05:41:00 +00:00
|
|
|
events.EventEmitter.call(this);
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
2015-04-14 06:19:14 +00:00
|
|
|
this.client = options.client;
|
2014-10-23 05:41:00 +00:00
|
|
|
this.views = {}; // map of ID -> view
|
2015-04-14 06:19:14 +00:00
|
|
|
this.formId = options.formId || 0;
|
2014-10-23 05:41:00 +00:00
|
|
|
|
|
|
|
this.onClientKeyPress = function(key, isSpecial) {
|
|
|
|
if(isSpecial) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(self.focusedView && self.focusedView.acceptsInput) {
|
|
|
|
key = 'string' === typeof key ? key : key.toString();
|
|
|
|
self.focusedView.onKeyPress(key, isSpecial);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.onClientSpecialKeyPress = function(keyName) {
|
|
|
|
if(self.focusedView && self.focusedView.acceptsInput) {
|
|
|
|
self.focusedView.onSpecialKeyPress(keyName);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-04-19 08:13:13 +00:00
|
|
|
this.viewActionListener = function(action) {
|
2014-10-23 05:41:00 +00:00
|
|
|
switch(action) {
|
|
|
|
case 'next' :
|
|
|
|
self.emit('action', { view : this, action : action });
|
|
|
|
self.nextFocus();
|
|
|
|
break;
|
|
|
|
|
2014-11-02 19:07:17 +00:00
|
|
|
case 'accept' : // :TODO: consider naming this 'done'
|
2014-10-23 05:41:00 +00:00
|
|
|
// :TODO: check if id is submit, etc.
|
2015-03-31 03:29:06 +00:00
|
|
|
if(self.focusedView && self.focusedView.submit) {
|
2014-11-02 19:07:17 +00:00
|
|
|
self.submitForm();
|
|
|
|
} else {
|
|
|
|
self.nextFocus();
|
|
|
|
}
|
2014-10-23 05:41:00 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-11-02 19:07:17 +00:00
|
|
|
this.submitForm = function() {
|
2015-03-26 05:23:14 +00:00
|
|
|
/*
|
|
|
|
Generate a form resonse. Example:
|
|
|
|
|
|
|
|
{
|
|
|
|
id : 0,
|
|
|
|
submitId : 1,
|
2015-03-30 03:47:48 +00:00
|
|
|
value : {
|
2015-03-26 05:23:14 +00:00
|
|
|
"1" : "hurp",
|
|
|
|
"2" : [ 'a', 'b', ... ],
|
|
|
|
"3 " 2,
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
*/
|
2014-11-02 19:07:17 +00:00
|
|
|
var formData = {
|
2015-03-26 05:23:14 +00:00
|
|
|
id : self.formId,
|
|
|
|
submitId : self.focusedView.id,
|
2015-03-30 03:47:48 +00:00
|
|
|
value : {},
|
2014-11-02 19:07:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
var viewData;
|
2015-04-19 08:13:13 +00:00
|
|
|
var view;
|
2014-11-02 19:07:17 +00:00
|
|
|
for(var id in self.views) {
|
|
|
|
try {
|
2015-04-19 08:13:13 +00:00
|
|
|
view = self.views[id];
|
|
|
|
viewData = view.getData();
|
|
|
|
if(!_.isUndefined(viewData)) {
|
|
|
|
if(_.isString(view.submitArgName)) {
|
|
|
|
formData.value[view.submitArgName] = viewData;
|
|
|
|
} else {
|
|
|
|
formData.value[id] = viewData;
|
|
|
|
}
|
2014-11-02 19:07:17 +00:00
|
|
|
}
|
|
|
|
} catch(e) {
|
2015-03-30 03:47:48 +00:00
|
|
|
Log.error(e); // :TODO: Log better ;)
|
2014-11-02 19:07:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.emit('submit', formData);
|
|
|
|
};
|
|
|
|
|
2015-04-14 06:19:14 +00:00
|
|
|
this.switchFocusEvent = function(event, view) {
|
|
|
|
if(self.emitSwitchFocus) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.emitSwitchFocus = true;
|
|
|
|
self.emit(event, view);
|
|
|
|
self.emitSwitchFocus = false;
|
|
|
|
};
|
|
|
|
|
2015-04-19 08:13:13 +00:00
|
|
|
this.handleSubmitAction = function(callingMenu, formData, conf) {
|
|
|
|
assert(_.isObject(conf));
|
|
|
|
assert(_.isString(conf.action));
|
|
|
|
|
|
|
|
var actionAsset = asset.parseAsset(conf.action);
|
|
|
|
assert(_.isObject(actionAsset));
|
|
|
|
|
|
|
|
var extraArgs;
|
|
|
|
if(conf.extraArgs) {
|
|
|
|
extraArgs = self.formatMenuArgs(conf.extraArgs);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(actionAsset.type) {
|
|
|
|
case 'method' :
|
|
|
|
if(_.isString(actionAsset.location)) {
|
|
|
|
// :TODO: allow omition of '.js'
|
|
|
|
var methodMod = require(paths.join(Config.paths.mods, actionAsset.location));
|
|
|
|
if(_.isFunction(methodMod[actionAsset.asset])) {
|
|
|
|
methodMod[actionAsset.asset](callingMenu, formData, extraArgs);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// local to current module
|
|
|
|
var currentModule = self.client.currentMenuModule;
|
|
|
|
if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) {
|
|
|
|
currentModule.menuMethods[actionAsset.asset](formData, extraArgs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'menu' :
|
|
|
|
// :TODO: update everythign to handle this format
|
|
|
|
self.client.gotoMenuModule( { name : actionAsset.asset, submitData : formData, extraArgs : extraArgs } );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-10-23 05:41:00 +00:00
|
|
|
this.attachClientEvents();
|
|
|
|
}
|
|
|
|
|
|
|
|
util.inherits(ViewController, events.EventEmitter);
|
|
|
|
|
|
|
|
ViewController.prototype.attachClientEvents = function() {
|
|
|
|
if(this.attached) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.client.on('key press', this.onClientKeyPress);
|
|
|
|
this.client.on('special key', this.onClientSpecialKeyPress);
|
|
|
|
|
|
|
|
this.attached = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
ViewController.prototype.detachClientEvents = function() {
|
|
|
|
if(!this.attached) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-19 08:13:13 +00:00
|
|
|
|
2014-10-23 05:41:00 +00:00
|
|
|
this.client.removeListener('key press', this.onClientKeyPress);
|
|
|
|
this.client.removeListener('special key', this.onClientSpecialKeyPress);
|
|
|
|
|
2014-11-04 07:34:54 +00:00
|
|
|
for(var id in this.views) {
|
|
|
|
this.views[id].removeAllListeners();
|
|
|
|
}
|
|
|
|
|
2014-10-23 05:41:00 +00:00
|
|
|
this.attached = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
ViewController.prototype.viewExists = function(id) {
|
|
|
|
return id in this.views;
|
|
|
|
};
|
|
|
|
|
|
|
|
ViewController.prototype.addView = function(view) {
|
|
|
|
assert(!this.viewExists(view.id), 'View with ID ' + view.id + ' already exists');
|
|
|
|
|
|
|
|
this.views[view.id] = view;
|
|
|
|
};
|
|
|
|
|
|
|
|
ViewController.prototype.getView = function(id) {
|
|
|
|
return this.views[id];
|
|
|
|
};
|
|
|
|
|
2015-04-12 05:48:41 +00:00
|
|
|
ViewController.prototype.getFocusedView = function() {
|
|
|
|
return this.focusedView;
|
|
|
|
};
|
|
|
|
|
2014-10-23 05:41:00 +00:00
|
|
|
ViewController.prototype.switchFocus = function(id) {
|
|
|
|
if(this.focusedView && this.focusedView.acceptsFocus) {
|
2015-04-14 06:19:14 +00:00
|
|
|
this.switchFocusEvent('leave', this.focusedView);
|
2014-10-23 05:41:00 +00:00
|
|
|
this.focusedView.setFocus(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
var view = this.getView(id);
|
|
|
|
if(view && view.acceptsFocus) {
|
2015-04-14 06:19:14 +00:00
|
|
|
this.switchFocusEvent('enter', view);
|
|
|
|
|
2014-10-23 05:41:00 +00:00
|
|
|
this.focusedView = view;
|
|
|
|
this.focusedView.setFocus(true);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ViewController.prototype.nextFocus = function() {
|
|
|
|
if(!this.focusedView) {
|
|
|
|
this.switchFocus(this.views[this.firstId].id);
|
|
|
|
} else {
|
|
|
|
var nextId = this.views[this.focusedView.id].nextId;
|
|
|
|
this.switchFocus(nextId);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ViewController.prototype.setViewOrder = function(order) {
|
|
|
|
var viewIdOrder = order || [];
|
|
|
|
|
|
|
|
if(0 === viewIdOrder.length) {
|
|
|
|
for(var id in this.views) {
|
2014-10-28 03:58:34 +00:00
|
|
|
if(this.views[id].acceptsFocus) {
|
|
|
|
viewIdOrder.push(id);
|
|
|
|
}
|
2014-10-23 05:41:00 +00:00
|
|
|
}
|
|
|
|
|
2014-10-28 03:58:34 +00:00
|
|
|
viewIdOrder.sort(function intSort(a, b) {
|
|
|
|
return a - b;
|
|
|
|
});
|
2014-10-23 05:41:00 +00:00
|
|
|
}
|
|
|
|
|
2014-10-31 22:25:11 +00:00
|
|
|
if(viewIdOrder.length > 0) {
|
|
|
|
var view;
|
|
|
|
var count = viewIdOrder.length - 1;
|
|
|
|
for(var i = 0; i < count; ++i) {
|
|
|
|
this.views[viewIdOrder[i]].nextId = viewIdOrder[i + 1];
|
|
|
|
}
|
2014-10-23 05:41:00 +00:00
|
|
|
|
2014-10-31 22:25:11 +00:00
|
|
|
this.firstId = viewIdOrder[0];
|
|
|
|
var lastId = viewIdOrder.length > 1 ? viewIdOrder[viewIdOrder.length - 1] : this.firstId;
|
|
|
|
this.views[lastId].nextId = this.firstId;
|
|
|
|
}
|
2014-10-23 05:41:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
ViewController.prototype.loadFromMCIMap = function(mciMap) {
|
|
|
|
var factory = new MCIViewFactory(this.client);
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
Object.keys(mciMap).forEach(function onMciEntry(name) {
|
|
|
|
var mci = mciMap[name];
|
|
|
|
var view = factory.createFromMCI(mci);
|
|
|
|
|
|
|
|
if(view) {
|
2015-04-19 08:13:13 +00:00
|
|
|
view.on('action', self.viewActionListener);
|
2014-11-04 07:34:54 +00:00
|
|
|
self.addView(view);
|
2014-10-31 22:25:11 +00:00
|
|
|
view.redraw(); // :TODO: This can result in double redraw() if we set focus on this item after
|
2014-10-23 05:41:00 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-04-19 08:13:13 +00:00
|
|
|
function setViewPropertiesFromMCIConf(view, conf) {
|
|
|
|
view.submit = conf.submit || false;
|
|
|
|
|
|
|
|
if(_.isArray(conf.items)) {
|
|
|
|
view.setItems(conf.items);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(_.isString(conf.text)) {
|
|
|
|
view.setText(conf.text);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(_.isString(conf.argName)) {
|
|
|
|
view.submitArgName = conf.argName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ViewController.prototype.loadFromPrompt = function(options, cb) {
|
|
|
|
assert(_.isObject(options));
|
|
|
|
//assert(_.isObject(options.promptConfig));
|
|
|
|
assert(_.isObject(options.callingMenu));
|
|
|
|
assert(_.isObject(options.callingMenu.menuConfig));
|
|
|
|
assert(_.isObject(options.callingMenu.menuConfig.promptConfig));
|
|
|
|
assert(_.isObject(options.mciMap));
|
|
|
|
|
|
|
|
var promptConfig = options.callingMenu.menuConfig.promptConfig;
|
|
|
|
var self = this;
|
|
|
|
var factory = new MCIViewFactory(this.client);
|
|
|
|
var initialFocusId = 1; // default to first
|
|
|
|
|
|
|
|
// :TODO: if 'submit' is not present anywhere default to last ID
|
|
|
|
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function createViewsFromMCI(callback) {
|
|
|
|
async.each(Object.keys(options.mciMap), function entry(name, nextItem) {
|
|
|
|
var mci = options.mciMap[name];
|
|
|
|
var view = factory.createFromMCI(mci);
|
|
|
|
|
|
|
|
if(view) {
|
|
|
|
view.on('action', self.viewActionListener);
|
|
|
|
|
|
|
|
self.addView(view);
|
|
|
|
|
|
|
|
view.redraw(); // :TODO: fix double-redraw if this is the item we set focus to!
|
|
|
|
}
|
|
|
|
|
|
|
|
nextItem(null);
|
|
|
|
},
|
|
|
|
function complete(err) {
|
|
|
|
self.setViewOrder();
|
|
|
|
callback(err);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function applyPromptConfig(callback) {
|
|
|
|
var highestId = 1;
|
|
|
|
var submitId;
|
|
|
|
|
|
|
|
async.each(Object.keys(promptConfig.mci), function entry(mci, nextItem) {
|
|
|
|
var mciMatch = mci.match(MCI_REGEXP); // :TODO: what about auto-generated IDs? Do they simply not apply to menu configs?
|
|
|
|
|
|
|
|
var viewId = parseInt(mciMatch[2]);
|
|
|
|
assert(!isNaN(viewId));
|
|
|
|
|
|
|
|
var view = self.getView(viewId);
|
|
|
|
var mciConf = promptConfig.mci[mci];
|
|
|
|
|
|
|
|
setViewPropertiesFromMCIConf(view, mciConf);
|
|
|
|
|
|
|
|
if(mciConf.focus) {
|
|
|
|
initialFocusId = viewId;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(view.submit) {
|
|
|
|
submitId = viewId;
|
|
|
|
}
|
|
|
|
|
|
|
|
nextItem(null);
|
|
|
|
},
|
|
|
|
function complete(err) {
|
|
|
|
|
|
|
|
// default to highest ID if no 'submit' entry present
|
|
|
|
if(!submitId) {
|
|
|
|
self.getView(highestId).submit = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(err);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function setupSubmit(callback) {
|
|
|
|
|
|
|
|
self.on('submit', function promptSubmit(formData) {
|
|
|
|
// :TODO: Need to come up with a way to log without dumping sensitive form data here, e.g. remove password, etc.
|
|
|
|
Log.trace( { formData : formData }, 'Prompt submit');
|
|
|
|
|
|
|
|
var actionAsset = asset.parseAsset(promptConfig.action);
|
|
|
|
assert(_.isObject(actionAsset));
|
|
|
|
|
|
|
|
var extraArgs;
|
|
|
|
if(promptConfig.extraArgs) {
|
|
|
|
extraArgs = self.formatMenuArgs(promptConfig.extraArgs);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self.handleSubmitAction(options.callingMenu, formData, promptConfig);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*var formattedArgs;
|
|
|
|
if(conf.args) {
|
|
|
|
formattedArgs = self.formatMenuArgs(conf.args);
|
|
|
|
}
|
|
|
|
|
|
|
|
var actionAsset = asset.parseAsset(conf.action);
|
|
|
|
assert(_.isObject(actionAsset));
|
|
|
|
|
|
|
|
if('method' === actionAsset.type) {
|
|
|
|
if(actionAsset.location) {
|
|
|
|
// :TODO: call with (client, args, ...) at least.
|
|
|
|
} else {
|
|
|
|
// local to current module
|
|
|
|
var currentMod = self.client.currentMenuModule;
|
|
|
|
if(currentMod.menuMethods[actionAsset.asset]) {
|
|
|
|
currentMod.menuMethods[actionAsset.asset](formattedArgs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if('menu' === actionAsset.type) {
|
|
|
|
self.client.gotoMenuModule( { name : actionAsset.asset, args : formattedArgs } );
|
|
|
|
}*/
|
|
|
|
});
|
|
|
|
|
|
|
|
callback(null);
|
|
|
|
},
|
|
|
|
function setInitialFocus(callback) {
|
|
|
|
if(initialFocusId) {
|
|
|
|
self.switchFocus(initialFocusId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
function complete(err) {
|
|
|
|
console.log(err)
|
|
|
|
cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
ViewController.prototype.loadFromPrompt = function(options, cb) {
|
|
|
|
assert(_.isObject(options));
|
|
|
|
assert(_.isObject(options.prompt));
|
|
|
|
assert(_.isObject(options.prompt.artInfo));
|
|
|
|
assert(_.isObject(options.prompt.artInfo.mciMap));
|
|
|
|
assert(_.isObject(options.prompt.config));
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Prompts are like simplified forms:
|
|
|
|
// * They do not contain submit information themselves; this must
|
|
|
|
// the owning menu: options.prompt.config
|
|
|
|
// * There is only one form in a prompt (e.g. form 0, but this is not explicit)
|
|
|
|
// * Only one MCI mapping: options.prompt.artInfo.mciMap
|
|
|
|
//
|
|
|
|
var self = this;
|
|
|
|
var factory = new MCIViewFactory(this.client);
|
|
|
|
var mciMap = options.prompt.artInfo.mciMap;
|
|
|
|
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function createViewsFromMCI(callback) {
|
|
|
|
async.each(Object.keys(mciMap), function mciEntry(name, nextItem) {
|
|
|
|
var mci = mciMap[name];
|
|
|
|
var view = factory.createFromMCI(mci);
|
|
|
|
|
|
|
|
if(view) {
|
|
|
|
self.addView(view);
|
|
|
|
view.redraw(); // :TODO: fix double-redraw if this is the item we set focus to!
|
|
|
|
}
|
|
|
|
|
|
|
|
nextItem(null);
|
|
|
|
},
|
|
|
|
function mciComplete(err) {
|
|
|
|
callback(err);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
],
|
|
|
|
function compelte(err) {
|
|
|
|
cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
*/
|
|
|
|
|
2015-03-30 03:47:48 +00:00
|
|
|
ViewController.prototype.loadFromMCIMapAndConfig = function(options, cb) {
|
|
|
|
assert(options.mciMap);
|
|
|
|
|
2015-04-04 20:41:04 +00:00
|
|
|
var factory = new MCIViewFactory(this.client);
|
|
|
|
var self = this;
|
|
|
|
var formIdKey = options.formId ? options.formId.toString() : '0';
|
|
|
|
var initialFocusId;
|
2015-04-06 06:18:08 +00:00
|
|
|
var formConfig;
|
2015-04-04 20:41:04 +00:00
|
|
|
|
2015-04-10 04:49:56 +00:00
|
|
|
var mciRegEx = /([A-Z]{2})([0-9]{1,2})/;
|
|
|
|
|
2015-04-04 20:41:04 +00:00
|
|
|
// :TODO: remove all the passing of fromConfig - use local
|
2015-04-05 07:15:04 +00:00
|
|
|
// :TODO: break all of this up ... a lot
|
2015-03-28 00:02:00 +00:00
|
|
|
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function getFormConfig(callback) {
|
2015-04-06 06:18:08 +00:00
|
|
|
menuUtil.getFormConfig(options.menuConfig, formIdKey, options.mciMap, function onFormConfig(err, fc) {
|
|
|
|
formConfig = fc;
|
|
|
|
|
2015-03-28 00:02:00 +00:00
|
|
|
if(err) {
|
2015-04-06 06:18:08 +00:00
|
|
|
// :TODO: fix logging of err here:
|
2015-04-19 08:13:13 +00:00
|
|
|
Log.trace(
|
2015-04-14 06:19:14 +00:00
|
|
|
{ err : err.toString(), mci : Object.keys(options.mciMap), formIdKey : formIdKey } ,
|
2015-04-06 06:18:08 +00:00
|
|
|
'Unable to load menu configuration');
|
2015-03-28 00:02:00 +00:00
|
|
|
}
|
2015-03-30 03:47:48 +00:00
|
|
|
|
2015-04-06 06:18:08 +00:00
|
|
|
callback(null);
|
2015-03-28 00:02:00 +00:00
|
|
|
});
|
|
|
|
},
|
2015-04-19 08:13:13 +00:00
|
|
|
function createViewsFromMCI(callback) {
|
2015-03-30 03:47:48 +00:00
|
|
|
async.each(Object.keys(options.mciMap), function onMciEntry(name, eachCb) {
|
|
|
|
var mci = options.mciMap[name];
|
2015-03-28 00:02:00 +00:00
|
|
|
var view = factory.createFromMCI(mci);
|
|
|
|
|
|
|
|
if(view) {
|
2015-04-19 08:13:13 +00:00
|
|
|
view.on('action', self.viewActionListener);
|
2015-03-28 00:02:00 +00:00
|
|
|
self.addView(view);
|
|
|
|
view.redraw(); // :TODO: This can result in double redraw() if we set focus on this item after
|
|
|
|
}
|
|
|
|
eachCb(null);
|
|
|
|
},
|
|
|
|
function eachMciComplete(err) {
|
|
|
|
self.setViewOrder();
|
2015-04-06 06:18:08 +00:00
|
|
|
callback(err);
|
2015-03-28 00:02:00 +00:00
|
|
|
});
|
|
|
|
},
|
2015-04-06 06:18:08 +00:00
|
|
|
function applyFormConfig(callback) {
|
2015-03-30 03:47:48 +00:00
|
|
|
if(formConfig) {
|
|
|
|
async.each(Object.keys(formConfig.mci), function onMciConf(mci, eachCb) {
|
2015-04-10 04:49:56 +00:00
|
|
|
var mciMatch = mci.match(mciRegEx); // :TODO: what about auto-generated IDs? Do they simply not apply to menu configs?
|
|
|
|
var viewId = parseInt(mciMatch[2]);
|
2015-03-31 03:29:06 +00:00
|
|
|
var view = self.getView(viewId);
|
2015-03-30 03:47:48 +00:00
|
|
|
var mciConf = formConfig.mci[mci];
|
|
|
|
|
|
|
|
// :TODO: Break all of this up ... and/or better way of doing it
|
|
|
|
if(mciConf.items) {
|
2015-03-31 03:29:06 +00:00
|
|
|
view.setItems(mciConf.items);
|
2015-03-30 03:47:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(mciConf.submit) {
|
2015-03-31 03:29:06 +00:00
|
|
|
view.submit = true; // :TODO: should really be actual value
|
2015-03-30 03:47:48 +00:00
|
|
|
}
|
|
|
|
|
2015-03-31 03:29:06 +00:00
|
|
|
if(mciConf.text) {
|
|
|
|
view.setText(mciConf.text);
|
|
|
|
}
|
|
|
|
|
2015-04-04 20:41:04 +00:00
|
|
|
if(mciConf.focus) {
|
|
|
|
initialFocusId = viewId;
|
|
|
|
}
|
2015-03-30 03:47:48 +00:00
|
|
|
|
|
|
|
eachCb(null);
|
|
|
|
},
|
|
|
|
function eachMciConfComplete(err) {
|
2015-04-06 06:18:08 +00:00
|
|
|
callback(err);
|
2015-03-30 03:47:48 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
callback(null);
|
|
|
|
}
|
|
|
|
},
|
2015-04-06 06:18:08 +00:00
|
|
|
function mapMenuSubmit(callback) {
|
2015-03-30 03:47:48 +00:00
|
|
|
if(formConfig) {
|
|
|
|
//
|
|
|
|
// If we have a 'submit' section, create a submit handler
|
|
|
|
// and map the various entries to menus/etc.
|
|
|
|
//
|
2015-04-02 04:13:29 +00:00
|
|
|
if(_.isObject(formConfig.submit)) {
|
2015-04-05 07:15:04 +00:00
|
|
|
// :TODO: If this model is kept, formData does not need to include actual data, just form ID & submitID
|
2015-04-17 04:29:53 +00:00
|
|
|
// we can get the rest here via each view in form -> getData()
|
2015-03-30 03:47:48 +00:00
|
|
|
self.on('submit', function onSubmit(formData) {
|
|
|
|
Log.debug( { formData : formData }, 'Submit form');
|
|
|
|
|
2015-04-02 04:13:29 +00:00
|
|
|
var confForFormId;
|
|
|
|
if(_.isObject(formConfig.submit[formData.submitId])) {
|
|
|
|
confForFormId = formConfig.submit[formData.submitId];
|
|
|
|
} else if(_.isObject(formConfig.submit['*'])) {
|
|
|
|
confForFormId = formConfig.submit['*'];
|
|
|
|
} else {
|
|
|
|
// no configuration for this submitId
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var formValueCompare = function(formDataValue, formConfigValue) {
|
|
|
|
//
|
|
|
|
// Any key(s) in formConfigValue must:
|
|
|
|
// 1) be present in formDataValue
|
|
|
|
// 2) must either:
|
|
|
|
// a) be set to null (wildcard/any)
|
|
|
|
// b) have matching values
|
|
|
|
//
|
|
|
|
var formConfigValueKeys = Object.keys(formConfigValue);
|
|
|
|
for(var k = 0; k < formConfigValueKeys.length; ++k) {
|
|
|
|
var memberKey = formConfigValueKeys[k];
|
|
|
|
|
|
|
|
// submit data contains config key?
|
|
|
|
if(!_.has(formDataValue, memberKey)) {
|
|
|
|
return false; // not present in what was submitted
|
|
|
|
}
|
|
|
|
|
|
|
|
if(null !== formConfigValue[memberKey] && formConfigValue[memberKey] !== formDataValue[memberKey]) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2015-03-31 03:29:06 +00:00
|
|
|
};
|
|
|
|
|
2015-04-04 20:41:04 +00:00
|
|
|
var conf;
|
2015-04-02 04:13:29 +00:00
|
|
|
for(var c = 0; c < confForFormId.length; ++c) {
|
2015-04-04 20:41:04 +00:00
|
|
|
conf = confForFormId[c];
|
|
|
|
if(_.isEqual(formData.value, conf.value, formValueCompare)) {
|
|
|
|
|
2015-04-05 07:15:04 +00:00
|
|
|
if(!conf.action) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-04-04 20:41:04 +00:00
|
|
|
var formattedArgs;
|
|
|
|
if(conf.args) {
|
|
|
|
formattedArgs = self.formatMenuArgs(conf.args);
|
|
|
|
}
|
2015-04-05 07:15:04 +00:00
|
|
|
|
|
|
|
var actionAsset = asset.parseAsset(conf.action);
|
|
|
|
assert(_.isObject(actionAsset));
|
|
|
|
|
|
|
|
if('method' === actionAsset.type) {
|
|
|
|
if(actionAsset.location) {
|
|
|
|
// :TODO: call with (client, args, ...) at least.
|
|
|
|
} else {
|
|
|
|
// local to current module
|
|
|
|
var currentMod = self.client.currentMenuModule;
|
|
|
|
if(currentMod.menuMethods[actionAsset.asset]) {
|
|
|
|
currentMod.menuMethods[actionAsset.asset](formattedArgs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if('menu' === actionAsset.type) {
|
|
|
|
self.client.gotoMenuModule( { name : actionAsset.asset, args : formattedArgs } );
|
|
|
|
}
|
2015-03-30 03:47:48 +00:00
|
|
|
}
|
2015-04-02 04:13:29 +00:00
|
|
|
}
|
2015-03-30 03:47:48 +00:00
|
|
|
});
|
2015-03-28 00:02:00 +00:00
|
|
|
}
|
2015-03-30 03:47:48 +00:00
|
|
|
}
|
2015-04-04 20:41:04 +00:00
|
|
|
|
|
|
|
callback(null);
|
|
|
|
},
|
|
|
|
function setInitialFocus(callback) {
|
|
|
|
if(initialFocusId) {
|
|
|
|
self.switchFocus(initialFocusId);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null);
|
2015-03-28 00:02:00 +00:00
|
|
|
}
|
|
|
|
],
|
|
|
|
function complete(err) {
|
|
|
|
if(cb) {
|
|
|
|
cb(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
2015-04-04 20:41:04 +00:00
|
|
|
|
|
|
|
ViewController.prototype.formatMCIString = function(format) {
|
|
|
|
var self = this;
|
|
|
|
var view;
|
|
|
|
|
|
|
|
return format.replace(/{(\d+)}/g, function replacer(match, number) {
|
|
|
|
view = self.getView(number);
|
|
|
|
|
|
|
|
if(!view) {
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
|
2015-04-17 04:29:53 +00:00
|
|
|
return view.getData();
|
2015-04-04 20:41:04 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
ViewController.prototype.formatMenuArgs = function(args) {
|
|
|
|
var self = this;
|
|
|
|
|
2015-04-05 07:15:04 +00:00
|
|
|
return _.mapValues(args, function val(value) {
|
2015-04-04 20:41:04 +00:00
|
|
|
if('string' === typeof value) {
|
2015-04-05 07:15:04 +00:00
|
|
|
return self.formatMCIString(value);
|
2015-04-04 20:41:04 +00:00
|
|
|
}
|
2015-04-05 07:15:04 +00:00
|
|
|
return value;
|
2015-04-04 20:41:04 +00:00
|
|
|
});
|
|
|
|
};
|