/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
define([ 'baja!',
'lex!js,webEditors',
'log!nmodule.webEditors.rc.fe.feDialogs',
'dialogs',
'jquery',
'Promise',
'underscore',
'bajaux/events',
'bajaux/Widget',
'bajaux/commands/Command',
'bajaux/util/ErrorDetailsWidget',
'nmodule/webEditors/rc/fe/fe',
'nmodule/webEditors/rc/fe/BaseWidget',
'nmodule/webEditors/rc/fe/ValueWithSummaryWidget',
'nmodule/webEditors/rc/fe/baja/util/compUtils',
'nmodule/webEditors/rc/fe/baja/util/typeUtils' ], function (
baja,
lexs,
log,
dialogs,
$,
Promise,
_,
events,
Widget,
Command,
ErrorDetailsWidget,
fe,
BaseWidget,
ValueWithSummaryWidget,
compUtils,
typeUtils) {
'use strict';
const { noop, omit, once, pick } = _;
const { LOAD_EVENT, MODIFY_EVENT } = events;
const { VALUE_READY_EVENT } = BaseWidget;
const [ jsLex, webEditorsLex ] = lexs;
const { isComplex } = typeUtils;
const logError = log.severe.bind(log);
const DEFAULT_DELAY = 200;
const ENTER_KEY = 13;
/**
* Functions for showing field editors in modal dialogs. Useful for prompting
* the user to enter values, edit individual slots, and fire actions.
*
* @exports nmodule/webEditors/rc/fe/feDialogs
*/
const feDialogs = {};
////////////////////////////////////////////////////////////////
// Support functions
////////////////////////////////////////////////////////////////
//TODO: check for operator flag
/**
* Ensures that a mounted `component` and Action `slot` param are present.
* If an actionArgument is provided ensure its at least a BValue.
*
* @private
* @inner
* @param {Object} params
*/
function validateActionParams(params) {
params = params || {};
const component = params.component;
if (!baja.hasType(component, 'baja:Component')) {
throw new Error('component required');
}
if (!component.isMounted()) {
throw new Error('component must be mounted');
}
const slot = component.getSlot(params.slot);
if (!slot || !slot.isAction()) {
throw new Error('Action slot required');
}
const actionArgument = params.actionArgument;
if (actionArgument !== undefined && (!baja.hasType(actionArgument) || !actionArgument.getType().isValue())) {
throw new Error('Action Arguments must be a Value');
}
}
/**
* Checks for `CONFIRM_REQUIRED` flag and shows confirmation dialog if
* needed.
*
* @private
* @inner
* @param {baja.Complex} comp
* @param {baja.Slot|String} slot
* @returns {Promise} promise to be resolved if no confirmation was
* needed or the user did confirm that the action should be invoked. Rejected
* if the user did not confirm invocation.
*/
function confirmInvoke(comp, slot) {
// eslint-disable-next-line promise/avoid-new
return new Promise((resolve, reject) => {
if (!(comp.getFlags(slot) & baja.Flags.CONFIRM_REQUIRED)) {
return resolve();
}
const display = comp.getDisplayName(slot);
dialogs.showOkCancel({
title: webEditorsLex.get('dialogs.confirmInvoke.title', display),
text: webEditorsLex.get('dialogs.confirmInvoke.content', display)
})
.ok(resolve)
.cancel(reject);
});
}
/**
* Build the editor in the given dialog.
*
* As the editor is created, initialized and loaded, progress events with
* those same names will be passed to the given progress handler. This way,
* someone using `feDialogs.showFor` can get callbacks for the actual editor
* instance as it is created, and add event handlers on it, for instance.
*
* @inner
* @param {Object} params fe params
* @param {JQuery} contentDiv
* @param {Function} progress
* @param {Dialog} dlg The Dialog instance.
* @returns {*}
*/
function buildEditor(params, contentDiv, progress, dlg) {
const parent = contentDiv.parent();
const { summary } = params;
contentDiv.detach();
if (summary) {
params = omit(params, 'summary');
}
let value;
return fe.params(params)
.then((feParams) => {
value = feParams.getValueToLoad();
let makeForParams = feParams;
if (summary) {
const { value } = feParams;
makeForParams = {
type: ValueWithSummaryWidget,
value,
properties: {
getConfig: () => feParams,
summary
}
};
}
return fe.makeFor(makeForParams);
})
.then((ed) => {
progress('created', ed);
return ed.initialize(contentDiv)
.then(() => {
ed.$dlg = dlg;
progress('initialized', ed);
return ed.load(value);
})
.then(() => {
progress('loaded', ed);
contentDiv.prependTo(parent);
return ed;
}, function (err) {
throw err;
});
});
}
function readAndDestroy(ed, shouldSave, onSaveError) {
const modified = ed.isModified();
return Promise.resolve(shouldSave && ed.save())
.catch((err) => {
const errorProm = onSaveError ? Promise.resolve(onSaveError(err)) : feDialogs.error(err);
return errorProm.catch(logError).then(() => {
throw err; //failed to validate - keep dialog open
});
})
.then(() => ed.read())
.then((value) => {
return Promise.resolve(modified && emitValueReady(ed, value))
.then(() => ed.destroy().catch(logError))
.then(() => value);
});
}
/**
* @param {module:bajaux/Widget} ed
* @param {*} value
* @returns {Promise}
*/
function emitValueReady(ed, value) {
return ed.emitAndWait(VALUE_READY_EVENT, value);
}
////////////////////////////////////////////////////////////////
// Exports
////////////////////////////////////////////////////////////////
/**
* Shows a field editor in a dialog.
*
* When the user clicks OK, the editor will be saved, committing any changes.
* The value that the user entered will be read from the editor and used to
* resolve the promise.
*
* @param {Object} params params to be passed to `fe.buildFor()`.
* @param {jQuery} [params.dom] if your widget type should be instantiated
* into a specific kind of DOM element, it can be passed in as a parameter.
* Note that the given element will be appended to the dialog element itself,
* so do not pass in an element that is already parented. If omitted, a `div`
* will be created and used.
* @param {String} [params.title] title for the dialog
* @param {String} [params.summary] (since Niagara 4.14) provide this to display a more detailed
* textual description about this prompt, e.g.: a description of its purpose, instructions on how
* to use the editor shown, any additional information the user may want.
* @param {Number} [params.delay=200] delay in ms to wait before showing a
* loading spinner. The spinner will disappear when the field editor has
* finished initializing and loading.
* @param {boolean} [params.save] set to false to specify that the dialog
* should *not* be saved on clicking OK - only the current value will be read
* from the editor and used to resolve the promise.
* @param {Function} [params.onSaveError] when this function is set and save
* is set to true, the error will be handed off to this method. Otherwise,
* when save is true feDialogs will show whatever error caused by the save in
* a different dialog. This may optionally return a promise.
* @param {Function} [params.progressCallback] pass a progress callback to
* receive notifications as the editor being shown goes through the stages
* of its life cycle (`created`, `initialized`, `loaded`), as well as whenever
* the editor is validated (`invalid`, `valid`).
* @param {Array.<module:dialogs~Button|string>} [params.buttons] as of Niagara 4.12,
* custom buttons can be specified. See examples for details.
* @param {Object.<string, Function>} [params.on] as of Niagara 4.12, custom handlers for
* `bajaux` events (and only `bajaux` events) can be specified. This is an object literal
* where the keys are `bajaux` event names and the values are event handler functions.
* See examples for details.
* @param {Function} [params.validate] as of Niagara 4.14, specify a custom validate function
* to ensure that the entered value is valid. If the user enters an invalid value, the OK button
* will be disabled. This function should throw an Error, return a rejected Promise, or return or
* resolve `false` to fail validation.
* @returns {Promise} promise to be resolved when the user has entered
* a value into the field editor and clicked OK, or rejected if the field
* could not be read. The promise will be resolved with the value that the
* user entered (or `null` if Cancel was clicked).
*
* @example
* feDialogs.showFor({
* value: 'enter a string here (max 50 chars)',
* properties: { max: 50 },
* progressCallback: function (msg, arg) {
* switch(msg) {
* case 'created': return console.log('editor created', arg);
* case 'initialized': return console.log('editor initialized', arg.jq());
* case 'loaded': return console.log('editor loaded', arg.value());
* case 'invalid': return console.log('validation error', arg);
* case 'valid': return console.log('value is valid', arg);
* }
* }
* })
* .then(function (str) {
* if (str === null) {
* console.log('you clicked cancel');
* } else {
* console.log('you entered: ' + str);
* }
* });
*
* @example
* <caption>Specify custom button handlers. If the user clicks one of these custom buttons,
* the showFor promise will be resolved with the value resolved by its handler.</caption>
*
* feDialogs.showFor({
* value: 'enter a string',
* buttons: [ {
* name: 'uppercase',
* displayName: 'Uppercase It',
* handler: (dialog, event, editor) => {
* // the arguments to the button handler are: the Dialog instance, the click event,
* // and the editor being shown in the dialog.
* // call `dialog.keepOpen` if you are not ready for the dialog to close. the dialog will
* // stay open and the promise will not be resolved yet.
*
* dialog.keepOpen();
* return editor.read().then((string) => editor.load(string.toUpperCase());
* }
* }, {
* name: 'lowercase',
* displayName: 'Lowercase It',
* handler: (dialog, event, editor) => {
* dialog.keepOpen();
* return editor.read().then((string) => editor.load(string.toLowerCase()));
* }
* }, {
* // default 'ok' behavior is to read the value and resolve the promise. you don't have to
* // specify a handler to do this.
* name: 'ok'
* } ]
* });
*
* @example
* <caption>The strings 'ok', 'cancel', 'yes', and 'no' are special - you can include them in the buttons
* parameter to get their default behavior.</caption>
*
* // only show the OK button, and resolve the promise with the entered value. 'yes' works the same.
* feDialogs.showFor({ value: 'enter a string', buttons: [ 'ok' ] });
*
* // only show the Cancel button, and resolve the promise with null. 'no' works the same.
* feDialogs.showFor({ value: 'your changes will not be used', buttons: [ 'cancel' ] });
*
* @example
* <caption>The buttons parameter can be an object literal, where the values are button
* definitions or handler functions.</caption>
*
* feDialogs.showFor({
* value: 'Value to Edit',
* buttons: {
* ok: () => 'my custom ok result', // the value can be just a handler function. the default display name will be used.
* cancel: {
* displayName: "Never Mind"
* // omit the handler, and default handler for "cancel" will resolve null.
* },
* yes: {}, // just an empty object will use the default display name and default handler.
* no: {
* handler: () => 'user clicked "no"' // include a handler to override the default handler.
* },
* delete: {
* // for anything other than 'ok', 'cancel', 'yes', or 'no', you'll need to provide a
* // display name - or else just the button name will be used.
* displayName: 'Delete Everything',
* handler: () => deleteEverything()
* },
* retry: shouldShowRetryButton() && { // falsy values will cause the button _not_ to be shown.
* displayName: 'Try Again',
* handler: () => letUserTryAgain()
* }
* }
* });
*
* @example
* <caption>Use the 'on' parameter to respond to any bajaux events that are triggered by the
* editor.</caption>
*
* const { MODIFY_EVENT } = events;
* feDialogs.showFor({
* value: 'edit me',
* properties: { max: 10 },
* on: {
* [MODIFY_EVENT]: (dialog, event, editor) {
* return editor.validate()
* .catch(() => alert('no more than 10 characters pls'));
* }
* }
* });
*/
feDialogs.showFor = function showFor(params) {
params = baja.objectify(params, 'value');
const {
delay,
dom,
on = {},
progressCallback,
summary,
title,
validate
} = params;
const contentDiv = (dom || $('<div/>'));
if (contentDiv.parent().length) {
return Promise.reject(new Error('element already parented'));
}
return normalizeButtons(params.buttons || [ 'ok', 'cancel' ])
// eslint-disable-next-line promise/avoid-new
.then((buttons) => new Promise(function (resolve, reject) {
let editor;
// when using a summary, the value editor is a child of a ValueWithSummaryWidget so will not
// appear until the loaded event. we have to rapid-fire them after the editor becomes available.
let innerEdAppeared = summary && once(() => {
const inner = getEditorForCaller(editor);
if (progressCallback) {
progressCallback('created', inner);
progressCallback('initialized', inner);
progressCallback('loaded', inner);
}
if (validate) {
applyValidator(inner, validate);
}
});
const progress = function (event, ed) {
if (event === 'created') {
editor = ed;
if (validate && !summary) {
applyValidator(ed, validate);
}
}
if (innerEdAppeared) {
if (event === 'loaded') {
innerEdAppeared();
innerEdAppeared = null; // allow later progress messages to propagate as normal
}
} else {
progressCallback && progressCallback(event, getEditorForCaller(ed));
}
};
const firstShown = once(function () {
/* wait until the content is visible then toggle its visibility
off and on to work around iOS -webkit-touch-scrolling issue */
contentDiv.toggle(0);
contentDiv.toggle(0);
return editor && editor.requestFocus && editor.requestFocus();
});
dialogs.show({
buttons: withoutHandlers(buttons),
delay: delay || DEFAULT_DELAY,
title,
layout: () => {
//layout the editor when the dialog lays out
firstShown();
return editor && editor.layout();
},
content: (dlg, content) => {
contentDiv.appendTo(content);
contentDiv.on(LOAD_EVENT + ' ' + MODIFY_EVENT, (e, ed) => {
if (ed === editor) {
editor.validate()
.then((value) => {
const okJq = dlg.buttonJq('ok');
if (okJq) {
okJq.attr('title', '');
dlg.enableButton("ok");
}
progress('valid', value);
}, (err) => {
const okJq = dlg.buttonJq('ok');
if (okJq) {
okJq.attr('title', String(err));
dlg.disableButton("ok");
}
progress('invalid', err);
});
}
});
contentDiv.on('keyup', function (e) {
/*
Previously code was put in to check for a 'keydown' before adding a checking
on 'keyup' on the 'ENTER_KEY'. But the reason for that change was lost, and
it was causing some dialogs to not close when an ENTER_KEY was pressed. From
what can be tell putting this back, is not causing an issue, but if a future
issue with ENTER_KEY and closing dialogs comes up, we might need to revisit this
code.
*/
if (e.which === ENTER_KEY) {
Widget.in(contentDiv).validate()
.then(() => dlg.click('ok'))
.catch(noop);
}
});
Object.keys(on).forEach((eventName) => {
const handler = on[eventName];
contentDiv.on(eventName, function (e, ed, ...rest) {
if (ed !== editor) { return; }
Promise.try(() => handler.apply(this, [ dlg, e, getEditorForCaller(ed), ...rest ]))
.catch(logError);
});
});
return buildEditor(params, contentDiv, progress, dlg)
.then((ed) => {
contentDiv.on(VALUE_READY_EVENT, (e, value) => {
emitValueReady(ed, value)
.then(() => ed.destroy().catch(logError))
.then(() => {
dlg.close();
resolve(value);
})
.catch(logError);
});
buttons.forEach((btn) => {
const handler = getButtonHandler(btn, ed, params);
dlg.on(btn.name, function (dlg) {
let keepOpen;
dlg.keepOpen = () => { keepOpen = true; };
return Promise.try(() => handler.apply(this, [ ...arguments, getEditorForCaller(ed) ]))
.then((result) => {
if (keepOpen) {
throw new Error();
}
delete dlg.keepOpen;
return ed.destroy().catch(logError)
.then(() => resolve(result));
}, (err) => {
logError(err);
throw err;
});
});
});
})
.catch((err) => {
content.text(String(err));
reject(err);
});
}
});
}));
};
function getButtonHandler(btn, ed, params) {
let { handler } = btn;
switch (btn.name) {
case 'ok':
case 'yes':
return handler || (() => {
const { save, onSaveError } = params;
const shouldSave = save !== false;
return readAndDestroy(ed, shouldSave, onSaveError);
});
case 'cancel':
case 'no':
return handler || (() => null);
default:
return handler || (() => {});
}
}
/**
* @param {Array.<module:dialogs~Button|string|bajaux/commands/Command>|object} buttons
* @returns {Promise.<Array.<module:dialogs~Button>>}
*/
function normalizeButtons(buttons) {
if (!Array.isArray(buttons)) {
buttons = Object.keys(buttons).map((name) => {
let button = buttons[name];
if (!button) {
return;
}
if (typeof button === 'function') {
button = { handler: button };
}
button.name = name;
return button;
});
}
let cmdCount = 0;
return Promise.all(buttons.filter((b) => !!b).map((btn) => {
if (btn instanceof Command) {
return btn.toDisplayName()
.then((displayName) => {
return {
name: 'cmd' + (cmdCount++),
displayName,
handler: () => btn.invoke()
};
});
}
if (typeof btn === 'string') {
btn = { name: btn };
}
let { name, displayName } = btn;
if (!displayName) {
switch (name) {
case 'ok':
case 'cancel':
case 'yes':
case 'no':
btn.displayName = jsLex.get('dialogs.' + name);
break;
default:
btn.displayName = name;
}
}
return btn;
}));
}
function withoutHandlers(buttons) {
return buttons.map((btn) => {
if (typeof btn === 'string') {
btn = { name: btn };
}
return omit(btn, 'handler');
});
}
/**
* Show an editor in a dialog, similar to `showFor`, but with the added
* expectation that the editor represents a one-time interaction, like a
* button click, after which the dialog can be immediately closed. In other
* words, the "click ok to close" functionality is embedded in the editor
* itself. Only a Cancel button will be shown in the dialog itself.
*
* In order for the dialog to close, the shown editor must trigger a
* `feDialogs.VALUE_READY_EVENT`, optionally with a read value. When this
* event is triggered, the dialog will be closed and the promise resolved
* with the value passed to the event trigger.
*
* @param {Object} params params to be passed to `fe.buildFor`
* @param {String} [params.title] title for the dialog
* @param {Number} [params.delay=200] delay in ms to wait before showing a
* loading spinner. The spinner will disappear when the field editor has
* finished initializing and loading.
* @param {Function} [params.progressCallback] pass a progress callback to
* receive notifications as the editor being shown goes through the stages
* of its life cycle (created, initialized, loaded).
* @returns {Promise} promise to be resolved when the editor has
* triggered its own value event. It will be resolved with any value passed
* to the event trigger, or with `null` if Cancel was clicked.
*
* @example
* <caption>Trigger a VALUE_READY_EVENT to cause the dialog to be closed.
* </caption>
*
* // ...
* MyEditor.prototype.doInitialize = function (dom) {
* dom.on('click', 'button', function () {
* dom.trigger(feDialogs.VALUE_READY_EVENT, [ 'my value' ]);
* });
* };
* //...
*
* feDialogs.selfClosing({
* type: MyEditor
* }}
* .then(function (value) {
* if (value === 'my value') {
* //success!
* }
* });
*/
feDialogs.selfClosing = function (params) {
params = baja.objectify(params, 'value');
const progress = params.progressCallback || $.noop;
let delay = params.delay;
if (typeof delay === 'undefined') { delay = DEFAULT_DELAY; }
// eslint-disable-next-line promise/avoid-new
return new Promise((resolve, reject) => {
dialogs.showCancel({
delay: delay,
title: params.title,
content: function (dlg, content) {
const contentDiv = $('<div/>').appendTo(content);
dlg.cancel(() => resolve(null));
buildEditor(params, contentDiv, progress)
.then((ed) => {
contentDiv.on(VALUE_READY_EVENT, (e, value) => {
ed.destroy()
.finally(() => {
dlg.close();
resolve(value);
});
});
})
.catch(reject);
}
});
});
};
/**
* Invoke an action on a mounted component. If the action requires a
* parameter, a field editor dialog will be shown to retrieve that argument
* from the user.
*
* @param {Object} params
* @param {baja.Component} params.component the component on which to invoke
* the action. Must be mounted.
* @param {String|baja.Slot} params.slot the action slot to invoke. Must be
* a valid Action slot.
* @param {baja.Value} [params.actionArgument] Starting in Niagara 4.10, this
* action argument can be used instead of showing a dialog to obtain
* the argument.
* @returns {Promise} promise to be resolved with the action return
* value if the action was successfully invoked, resolved with `null` if
* the user clicked Cancel, or rejected if the parameters were invalid or the
* action could not be invoked.
*/
feDialogs.action = function action(params) {
try {
validateActionParams(params);
} catch (e) {
return Promise.reject(e);
}
function performInvocation(comp, slot, actionArgument) {
const actionArgumentProvided = actionArgument !== undefined;
let valueParam;
return Promise.resolve(actionArgumentProvided || compUtils.resolveActionParams(comp, slot))
.then((params) => {
if (actionArgumentProvided) {
return actionArgument;
}
if (params === undefined) {
return;
}
const { value, title, type, properties } = params;
valueParam = value;
return feDialogs.showFor({ value, title, type, properties });
})
.then((readValue) => {
if (readValue === null) {
// user clicked cancel to parameter dialog
return null;
}
return comp.invoke({
//complexes are always edit by ref.
value: isComplex(valueParam) ? valueParam : readValue,
slot: slot
});
});
}
const comp = params.component;
const slot = params.slot;
const actionArgument = params.actionArgument;
return confirmInvoke(comp, slot)
.then(() => performInvocation(comp, slot, actionArgument), () => /* invocation canceled */ null)
.catch((err) => feDialogs.error(err).then(() => { throw err; }));
};
/**
* A simple mechanism for editing multiple properties at once.
*
* @param {module:nmodule/webEditors/rc/fe/feDialogs~EditableJSON|baja.Complex} props a JSON object representing the
* properties to edit. May be nested. You can also simply pass a Complex to edit the slots of
* that Complex. See examples.
* @param {object} [params]
* @param {baja.Component} [params.ordBase] if field editors may need to resolve ORDs themselves,
* pass `ordBase` to allow them to successfully resolve ORDs even when offline.
* @param {string} [params.title] optional dialog title
* @param {string} [params.summary] optional summary details for dialog
* @param {Function} [params.validate] optional validation function to ensure that the entered
* value is valid. If the user enters an invalid value, the OK button will be disabled. This
* function should throw an Error, return a rejected Promise, or return or resolve `false` to fail
* validation. It will receive a JSON object or a Complex, depending on what was passed as the
* `props` argument.
* @returns {Promise.<object|null>} the newly entered values, or null if user clicked Cancel
* @since Niagara 4.14
*
* @example
* <caption>Edit a simple key->value map.</caption>
* return feDialogs.props({ foo: 'bar' }); // resolves an object with user-edited "foo" property
*
* @example
* <caption>Edit a nested key->value map.</caption>
* return feDialogs.props({
* user: { value: { firstName: 'Moe', lastName: 'Howard' } }
* }); // resolves an object with a "user" property containing user-editor "firstName" and "lastName"
*
* @example
* <caption>Specify display name</caption>
* return feDialogs.props({
* foo: { displayName: 'Your foo value', value: 'bar' }
* }); // resolves an object with user-entered "foo" property, but user was shown customized display name
*
* @example
* <caption>Use display name from lexicon</caption>
* return feDialogs.props({
* overrideValue: { displayName: '%lexicon(control:override.value)%', value: 0 }
* }); // resolves an object with user-entered "overrideValue" property but user was shown "Override Value" from lexicon
*
* @example
* <caption>Specify flags</caption>
* return feDialogs.props({
* hidden: { value: 'hiddenValue', hidden: true },
* readonly: { value: 'readonlyValue', readonly: true }
* }); // resolves an object with "hidden" and "readonly" values. User did not see "hidden" and was not able to edit "readonly".
*
* @example
* <caption>Specify properties</caption>
* return feDialogs.props({
* percent: { value: 0, properties: { min: 0, max: 100 } }
* }); // resolves an object with "percent" value. Editor was validated to be between 0 and 100.
*
* @example
* <caption>Edit a Complex directly</caption>
* const comp = baja.$('control:NumericWritable');
* return feDialogs.props(comp); // please note that the input Complex will be *saved* when the user clicks OK
*
* @example
* <caption>Specify title and summary</caption>
* return feDialogs.props({ foo: 'bar' }, {
* title: 'Foo Dialog',
* summary: 'Please enter your foo value'
* });
*/
feDialogs.props = function (props, params = {}) {
const { title, summary, validate } = params;
return feDialogs.showFor({
type: 'nmodule/webEditors/rc/fe/JSONPropertySheet', // no circular dependency
value: props,
formFactor: 'max',
readonly: params.readonly,
title,
summary,
validate,
properties: pick(params, 'ordBase'),
dom: $('<div class="-t-feDialogs-props-dialog"></div>')
});
};
/**
* When the user passes a `summary` parameter the actual editor shown in the dialog will be a
* ValueSummaryWidget. But the user's own handlers for bajaux events and button clicks don't want
* to receive a ValueSummaryWidget - that's internal details - if the caller showFor()s a String,
* they want to interact with a StringEditor. Give them that StringEditor.
*
* @param {module:bajaux/Widget} ed the editor shown in the dialog
* @returns {module:bajaux/Widget} the editor the caller expects to interact with
*/
function getEditorForCaller(ed) {
return ed instanceof ValueWithSummaryWidget ? ed.getInnerValueEditor() : ed;
}
function applyValidator(ed, validate) {
ed.validators().add((v) => {
return Promise.try(() => validate(v))
.then((result) => {
if (result === false) {
throw new Error();
}
});
});
}
/**
* Show details about an error.
*
* @param {Error|*} err
* @param {Object} [params]
* @param {String} [params.title]
* @param {module:bajaux/commands/Command} [params.command] An optional Command to help display information on which Command failed
* @param {String} [params.messageSummary] An optional messageSummary to prepend to the Error.
* @returns {Promise}
*/
feDialogs.error = function (err, params = {}) {
logError(err);
return ErrorDetailsWidget.dialog(err, params);
};
return (feDialogs);
});
/**
* JSON format for data intended to be shown to, and edited by, the user using a Property Sheet.
* This allows for more intuitive user editing of nested/annotated data, using the familiar Property
* Sheet interface, without requiring the developer to translate a bunch of non-Niagara data into a
* Component and back.
*
* @typedef {Object.<string, module:nmodule/webEditors/rc/fe/feDialogs~EditableJSON|baja.Complex|*>} module:nmodule/webEditors/rc/fe/feDialogs~EditableJSON
* @property {string|number|boolean} value the value to be edited
* @property {string} [displayName] display name for the edited value, in lexicon format. If
* omitted, the string key will be used.
* @property {boolean} [hidden] true if this value should be hidden from the user for editing, but
* still included in the results
* @property {boolean} [readonly] true if this value should be readonly
* @property {Object.<string, *>} [properties] any additional bajaux properties that should be
* applied to the field editor for this value
*
* @see module:nmodule/webEditors/rc/fe/feDialogs#props
*/