nakedobjects.spa
Version:
Single Page Application client for a Naked Objects application.
404 lines • 18.3 kB
JavaScript
import { CommandResult } from './command-result';
import * as Routedata from '../route-data';
import * as Models from '../models';
import * as Usermessages from '../user-messages';
import map from 'lodash/map';
import some from 'lodash/some';
import filter from 'lodash/filter';
import every from 'lodash/every';
import each from 'lodash/each';
import keys from 'lodash/keys';
import forEach from 'lodash/forEach';
import findIndex from 'lodash/findIndex';
import * as Commandresult from './command-result';
var Command = (function () {
function Command(urlManager, location, commandFactory, context, mask, error, configService, ciceroContext, ciceroRenderer) {
this.urlManager = urlManager;
this.location = location;
this.commandFactory = commandFactory;
this.context = context;
this.mask = mask;
this.error = error;
this.configService = configService;
this.ciceroContext = ciceroContext;
this.ciceroRenderer = ciceroRenderer;
this.keySeparator = configService.config.keySeparator;
}
Command.prototype.execute = function () {
var result = new CommandResult();
//TODO Create outgoing Vm and copy across values as needed
if (!this.isAvailableInCurrentContext()) {
return this.returnResult("", Usermessages.commandNotAvailable(this.fullCommand));
}
//TODO: This could be moved into a pre-parse method as it does not depend on context
if (this.argString == null) {
if (this.minArguments > 0) {
return this.returnResult("", Usermessages.noArguments);
}
}
else {
var args = this.argString.split(",");
if (args.length < this.minArguments) {
return this.returnResult("", Usermessages.tooFewArguments);
}
else if (args.length > this.maxArguments) {
return this.returnResult("", Usermessages.tooManyArguments);
}
}
return this.doExecute(this.argString, this.chained, result);
};
Command.prototype.returnResult = function (input, output, changeState, stopChain) {
changeState = changeState ? changeState : function () { };
return Promise.resolve({ input: input, output: output, changeState: changeState, stopChain: !!stopChain });
};
Command.prototype.mayNotBeChained = function (rider) {
if (rider === void 0) { rider = ""; }
return Usermessages.mayNotbeChainedMessage(this.fullCommand, rider);
};
Command.prototype.checkMatch = function (matchText) {
if (this.fullCommand.indexOf(matchText) !== 0) {
throw new Error(Usermessages.noSuchCommand(matchText));
}
};
//argNo starts from 0.
//If argument does not parse correctly, message will be passed to UI and command aborted.
Command.prototype.argumentAsString = function (argString, argNo, optional, toLower) {
if (optional === void 0) { optional = false; }
if (toLower === void 0) { toLower = true; }
if (!argString)
return undefined;
if (!optional && argString.split(",").length < argNo + 1) {
throw new Error(Usermessages.tooFewArguments);
}
var args = argString.split(",");
if (args.length < argNo + 1) {
if (optional) {
return undefined;
}
else {
throw new Error(Usermessages.missingArgument(argNo + 1));
}
}
return toLower ? args[argNo].trim().toLowerCase() : args[argNo].trim(); // which may be "" if argString ends in a ','
};
//argNo starts from 0.
Command.prototype.argumentAsNumber = function (args, argNo, optional) {
if (optional === void 0) { optional = false; }
var arg = this.argumentAsString(args, argNo, optional);
if (!arg && optional)
return null;
var number = parseInt(arg);
if (isNaN(number)) {
throw new Error(Usermessages.wrongTypeArgument(argNo + 1));
}
return number;
};
Command.prototype.parseInt = function (input) {
var number = parseInt(input);
if (isNaN(number)) {
throw new Error(Usermessages.isNotANumber(input));
}
return number;
};
//Parses '17, 3-5, -9, 6-' into two numbers
Command.prototype.parseRange = function (arg) {
if (!arg) {
arg = "1-";
}
var clauses = arg.split("-");
var range = { start: null, end: null };
switch (clauses.length) {
case 1: {
var firstValue = clauses[0];
range.start = firstValue ? this.parseInt(firstValue) : null;
range.end = range.start;
break;
}
case 2: {
var firstValue = clauses[0];
var secondValue = clauses[1];
range.start = firstValue ? this.parseInt(firstValue) : null;
range.end = secondValue ? this.parseInt(secondValue) : null;
break;
}
default:
throw new Error(Usermessages.tooManyDashes);
}
if ((range.start != null && range.start < 1) || (range.end != null && range.end < 1)) {
throw new Error(Usermessages.mustBeGreaterThanZero);
}
return range;
};
Command.prototype.getContextDescription = function () {
//todo
return null;
};
Command.prototype.routeData = function () {
return this.urlManager.getRouteData().pane1;
};
//Helpers delegating to RouteData
Command.prototype.isHome = function () {
return this.urlManager.isHome();
};
Command.prototype.isObject = function () {
return this.urlManager.isObject();
};
Command.prototype.getObject = function () {
var _this = this;
var oid = Models.ObjectIdWrapper.fromObjectId(this.routeData().objectId, this.keySeparator);
//TODO: Consider view model & transient modes?
return this.context.getObject(1, oid, this.routeData().interactionMode).then(function (obj) {
if (_this.routeData().interactionMode === Routedata.InteractionMode.Edit) {
return _this.context.getObjectForEdit(1, obj);
}
else {
return obj; //To wrap a known object as a promise
}
});
};
Command.prototype.isList = function () {
return this.urlManager.isList();
};
Command.prototype.getList = function () {
var routeData = this.routeData();
//TODO: Currently covers only the list-from-menu; need to cover list from object action
return this.context.getListFromMenu(routeData, routeData.page, routeData.pageSize);
};
Command.prototype.isMenu = function () {
return !!this.routeData().menuId;
};
Command.prototype.getMenu = function () {
return this.context.getMenu(this.routeData().menuId);
};
Command.prototype.isDialog = function () {
return !!this.routeData().dialogId;
};
Command.prototype.isMultiChoiceField = function (field) {
var entryType = field.entryType();
return entryType === Models.EntryType.MultipleChoices || entryType === Models.EntryType.MultipleConditionalChoices;
};
Command.prototype.getActionForCurrentDialog = function () {
var _this = this;
var dialogId = this.routeData().dialogId;
if (this.isObject()) {
return this.getObject().then(function (obj) { return _this.context.getInvokableAction(obj.actionMember(dialogId)); });
}
else if (this.isMenu()) {
return this.getMenu().then(function (menu) { return _this.context.getInvokableAction(menu.actionMember(dialogId)); }); //i.e. return a promise
}
return Promise.reject(new Models.ErrorWrapper(Models.ErrorCategory.ClientError, Models.ClientErrorCode.NotImplemented, "List actions not implemented yet"));
};
//Tests that at least one collection is open (should only be one).
//TODO: assumes that closing collection removes it from routeData NOT sets it to Summary
Command.prototype.isCollection = function () {
return this.isObject() && some(this.routeData().collections);
};
Command.prototype.closeAnyOpenCollections = function () {
var _this = this;
var open = this.ciceroRenderer.openCollectionIds(this.routeData());
forEach(open, function (id) { return _this.urlManager.setCollectionMemberState(id, Routedata.CollectionViewState.Summary); });
};
Command.prototype.isTable = function () {
return false; //TODO
};
Command.prototype.isEdit = function () {
return this.routeData().interactionMode === Routedata.InteractionMode.Edit;
};
Command.prototype.isForm = function () {
return this.routeData().interactionMode === Routedata.InteractionMode.Form;
};
Command.prototype.isTransient = function () {
return this.routeData().interactionMode === Routedata.InteractionMode.Transient;
};
Command.prototype.matchingProperties = function (obj, match) {
var props = map(obj.propertyMembers(), function (prop) { return prop; });
if (match) {
props = this.matchFriendlyNameAndOrMenuPath(props, match);
}
return props;
};
Command.prototype.matchingCollections = function (obj, match) {
var allColls = map(obj.collectionMembers(), function (action) { return action; });
if (match) {
return this.matchFriendlyNameAndOrMenuPath(allColls, match);
}
else {
return allColls;
}
};
Command.prototype.matchingParameters = function (action, match) {
var params = map(action.parameters(), function (p) { return p; });
if (match) {
params = this.matchFriendlyNameAndOrMenuPath(params, match);
}
return params;
};
Command.prototype.matchFriendlyNameAndOrMenuPath = function (reps, match) {
var clauses = match ? match.split(" ") : [];
//An exact match has preference over any partial match
var exactMatches = filter(reps, function (rep) {
var path = rep.extensions().menuPath();
var name = rep.extensions().friendlyName().toLowerCase();
return match === name ||
(!!path && match === path.toLowerCase() + " " + name) ||
every(clauses, function (clause) { return name === clause || (!!path && path.toLowerCase() === clause); });
});
if (exactMatches.length > 0)
return exactMatches;
return filter(reps, function (rep) {
var path = rep.extensions().menuPath();
var name = rep.extensions().friendlyName().toLowerCase();
return every(clauses, function (clause) { return name.indexOf(clause) >= 0 || (!!path && path.toLowerCase().indexOf(clause) >= 0); });
});
};
Command.prototype.findMatchingChoicesForRef = function (choices, titleMatch) {
return choices ? filter(choices, function (v) { return v.toString().toLowerCase().indexOf(titleMatch.toLowerCase()) >= 0; }) : [];
};
Command.prototype.findMatchingChoicesForScalar = function (choices, titleMatch) {
if (choices == null) {
return [];
}
var labels = keys(choices);
var matchingLabels = filter(labels, function (l) { return l.toString().toLowerCase().indexOf(titleMatch.toLowerCase()) >= 0; });
var result = new Array();
switch (matchingLabels.length) {
case 0:
break; //leave result empty
case 1:
//Push the VALUE for the key
//For simple scalars they are the same, but not for Enums
result.push(choices[matchingLabels[0]]);
break;
default:
//Push the matching KEYs, wrapped as (pseudo) Values for display in message to user
//For simple scalars the values would also be OK, but not for Enums
forEach(matchingLabels, function (label) { return result.push(new Models.Value(label)); });
break;
}
return result;
};
Command.prototype.handleErrorResponse = function (err, getFriendlyName) {
var _this = this;
if (err.invalidReason()) {
return this.returnResult("", err.invalidReason());
}
var msg = Usermessages.pleaseCompleteOrCorrect;
each(err.valuesMap(), function (errorValue, fieldId) {
msg += _this.fieldValidationMessage(errorValue, function () { return getFriendlyName(fieldId); });
});
return this.returnResult("", msg);
};
Command.prototype.handleErrorResponseNew = function (err, getFriendlyName) {
var _this = this;
if (err.invalidReason()) {
return this.returnResult("", err.invalidReason());
}
var msg = Usermessages.pleaseCompleteOrCorrect;
each(err.valuesMap(), function (errorValue, fieldId) {
msg += _this.fieldValidationMessage(errorValue, function () { return getFriendlyName(fieldId); });
});
return this.returnResult("", msg);
};
Command.prototype.validationMessage = function (reason, value, fieldFriendlyName) {
if (reason) {
var prefix = fieldFriendlyName + ": ";
var suffix = reason === Usermessages.mandatory ? Usermessages.required : value + " " + reason;
return "" + prefix + suffix + "\n";
}
return "";
};
Command.prototype.fieldValidationMessage = function (errorValue, fieldFriendlyName) {
var reason = errorValue.invalidReason;
return this.validationMessage(reason, errorValue.value, fieldFriendlyName());
};
Command.prototype.valueForUrl = function (val, field) {
var _this = this;
if (val.isNull())
return val;
var fieldEntryType = field.entryType();
if (fieldEntryType !== Models.EntryType.FreeForm || field.isCollectionContributed()) {
if (this.isMultiChoiceField(field) || field.isCollectionContributed()) {
var valuesFromRouteData_1 = new Array();
if (field instanceof Models.Parameter) {
var rd = Commandresult.getParametersAndCurrentValue(field.parent, this.context)[field.id()];
if (rd)
valuesFromRouteData_1 = rd.list(); //TODO: what if only one?
}
else if (field instanceof Models.PropertyMember) {
var obj = field.parent;
var props = this.context.getObjectCachedValues(obj.id());
var rd = props[field.id()];
if (rd)
valuesFromRouteData_1 = rd.list(); //TODO: what if only one?
}
var vals = [];
if (val.isReference() || val.isScalar()) {
vals = new Array(val);
}
else if (val.isList()) {
vals = val.list();
}
valuesFromRouteData_1 = valuesFromRouteData_1 || [];
forEach(vals, function (v) { return _this.addOrRemoveValue(valuesFromRouteData_1, v); });
if (vals[0].isScalar()) {
var scalars = map(valuesFromRouteData_1, function (v) { return v.scalar(); });
return new Models.Value(scalars);
}
else {
var links = map(valuesFromRouteData_1, function (v) { return ({ href: v.link().href(), title: Models.withUndefined(v.link().title()) }); });
return new Models.Value(links.length > 0 ? links : null);
}
}
if (val.isScalar()) {
return val;
}
// reference
return this.leanLink(val);
}
if (val.isScalar()) {
if (val.isNull()) {
return new Models.Value("");
}
return val;
//TODO: consider these options:
// if (from.value instanceof Date) {
// return new Value((from.value as Date).toISOString());
// }
// return new Value(from.value as number | string | boolean);
}
if (val.isReference()) {
return this.leanLink(val);
}
return null;
};
Command.prototype.leanLink = function (val) {
return new Models.Value({ href: val.link().href(), title: val.link().title() });
};
Command.prototype.addOrRemoveValue = function (valuesFromRouteData, val) {
var index;
var valToAdd;
if (val.isScalar()) {
valToAdd = val;
index = findIndex(valuesFromRouteData, function (v) { return v.scalar() === val.scalar(); });
}
else {
valToAdd = this.leanLink(val);
index = findIndex(valuesFromRouteData, function (v) { return v.link().href() === valToAdd.link().href(); });
}
if (index > -1) {
valuesFromRouteData.splice(index, 1);
}
else {
valuesFromRouteData.push(valToAdd);
}
};
Command.prototype.setFieldValueInContext = function (field, val) {
this.context.cacheFieldValue(this.routeData().dialogId, field.id(), val);
};
Command.prototype.setPropertyValueinContext = function (obj, property, urlVal) {
this.context.cachePropertyValue(obj, property, urlVal);
};
return Command;
}());
export { Command };
//# sourceMappingURL=Command.js.map