/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/*
* dev note: be super careful about adding dependencies to other editor types
* here. other editors may depend on this module and it becomes circular
* dependency city.
*/
define([ 'baja!',
'log!nmodule.webEditors.rc.fe.fe',
'Promise',
'underscore',
'bajaux/Properties',
'bajaux/Widget',
'nmodule/webEditors/rc/fe/registry/StationRegistry',
'nmodule/webEditors/rc/fe/feUtils',
'nmodule/webEditors/rc/fe/registry/NiagaraWidgetManager' ], function defineFE(
baja,
log,
Promise,
_,
Properties,
Widget,
StationRegistry,
feUtils,
NiagaraWidgetManager) {
'use strict';
/**
* Functions for registering, looking up, and instantiating editors for
* certain Baja types.
*
* @exports nmodule/webEditors/rc/fe/fe
*/
const fe = {};
const deriveSpecifiedConstructor = feUtils.deriveSpecifiedConstructor,
formFactorToInterface = feUtils.formFactorToInterface,
toRegistryQuery = feUtils.toRegistryQuery;
var logWarning = log.warning.bind(log);
const mgr = new NiagaraWidgetManager({
registry: StationRegistry.getInstance()
});
////////////////////////////////////////////////////////////////
// Support functions
////////////////////////////////////////////////////////////////
function formFactorsToInterfaces(formFactors) {
return _.map(formFactors || [], formFactorToInterface);
}
function getReg() {
return StationRegistry.getInstance();
}
////////////////////////////////////////////////////////////////
// Exports
////////////////////////////////////////////////////////////////
/**
* @private
* @typedef {Object} FeSpecificParams
* @property {baja.Complex} [complex] if given (with `slot` as well), will
* instantiate a complex editor that will save changes back to the `Complex`
* instance.
* @property {baja.Slot|String} [slot] if given (with `complex` as well), will
* instantiate an editor for the value at this slot.
* @property {Object|module:bajaux/Properties} [properties] the bajaux
* Properties the new widget should have. If the Property `uxFieldEditor` is
* present, its string value will be used to determine the type of widget to
* create. This can be a type spec that resolves to a `web:IJavaScript` type,
* or a RequireJS module ID.
* @property {Object|baja.Facets} [facets] (Deprecated: use `properties`) additional Facets information to
* apply to the new widget. Facets will be added as hidden transient
* bajaux Properties. (If a Property with the same key is also given, it will
* be overridden by the facet.)
* @property {Object} [data] an object literal containing any additional
* values to be passed to the new widget's constructor
*/
/**
* This type describes the available parameters to be passed to the various
* methods on `fe`. These values will be used both to look up the type of the
* desired editor, and also to construct that editor. In other words, the
* data to look up a widget will also be used in the same turn to construct
* that widget. See {@link module:bajaux/Widget}
*
* @typedef {module:bajaux/lifecycle/WidgetManager~BuildParams | FeSpecificParams} module:nmodule/webEditors/rc/fe/fe~FeParams
*/
/**
* @private
* @returns {module:nmodule/webEditors/rc/fe/registry/NiagaraWidgetManager} the
* `WidgetManager` used by `fe` to perform widget lookups
*/
fe.getWidgetManager = function () {
return mgr;
};
/**
* This function takes an object literal (passed for `makeFor` or `buildFor`)
* and performs some processing on it, adding some information needed to
* build editors.
*
* It will take values that Workbench "assumes to be present" in the Context
* (`timeFormat` and `unitConversion` Facets, which get injected via General
* Options) and inject them into the `facets` property. This way, editors
* instantiated via `makeFor` and `buildFor` will always have access to these.
*
* Contractual note: the input object should be compatible with the
* `Widget` constructor; the output object will be as well. That is, if
* your input object can be used to build a `Widget`, the output object
* can as well.
*
* @private
* @param {module:nmodule/webEditors/rc/fe/fe~FeParams} params
* @returns {Promise} promise to be resolved with an object with extra
* `fe`-related information
*/
fe.params = function (params) {
if (params === undefined || params === null) {
return Promise.reject(new Error('value required'));
}
if (typeof params !== 'object') {
params = { value: params };
}
if (params.facets) {
logWarning(new Error('passing facets param to fe is deprecated ' +
'- use properties instead'));
}
return mgr.buildContext(params)
.then((cx) => {
const { value } = cx;
return _.extend({}, params, cx.constructorParams, {
getValueToLoad: () => {
if (typeof value === 'undefined') {
throw new Error('could not determine value to load');
}
return value;
},
getWidgetConstructor: () => cx.widgetConstructor || null
});
});
};
/**
* Registers a RequireJS module to a baja Type. This takes a RequireJS
* module ID string which resolves to a module exporting a constructor for a
* {@link module:bajaux/Widget|Widget} subclass.
*
* @param {Type|String} type
* @param {String} module RequireJS module ID
* @param {Object} [params]
* @param {Array.<String>} [params.formFactors] form factors that this editor
* should support
* @returns {Promise} promise to be resolved after the module
* registration is complete. Note that `getDefaultConstructor()`, `makeFor()`,
* etc. will still work (with some possible extra network calls) before the
* promise is fully resolved. Promise will be rejected if RequireJS is unable
* to resolve a given module ID.
*
* @example
* <caption>Register StringEditor on baja:String, so that it can be used to
* build "mini" editors for Strings.</caption>
* fe.register('baja:String', 'nmodule/webEditors/rc/fe/baja/StringEditor', {
* formFactors: [ Widget.formfactor.mini ]
* });
*/
fe.register = function register(type, module, params) {
if (typeof module !== 'string') {
throw new Error('module required');
}
var interfaces = formFactorsToInterfaces(params && params.formFactors);
if (interfaces.indexOf('web:IFormFactor') < 0) {
interfaces.push('web:IFormFactor');
}
return getReg().register(type, {
rjs: module,
tags: interfaces
});
};
/**
* Retrieve the `Widget` constructor function registered for the given Type.
*
* @param {String|Type} type
* @param {Object} [params]
* @param {Array.<String|Number>} [params.formFactors] describes the form
* factors that the resolved constructor is required to support. These can
* be Strings referencing a form factor property on
* `bajaux/Widget.formfactor`, or the value itself. If a constructor matches
* *any* of these form factors it will be returned (union, not intersection).
* @param {Object} [params.properties] pass the widget properties that can help
* determine 'a' Widget constructor. For example, 'uxFieldEditor'.
* @returns {Promise.<Function>} a promise to be resolved with the constructor
* function for the given Type, or with `undefined` if no constructor is
* registered. Note that if an invalid RequireJS module ID was passed to
* `fe.register()`, it will still look up the supertype chain in an attempt
* to resolve *something*.
*
* @example
* function StringEditor() {} //extends Widget
* fe.register('baja:String', StringEditor, {
* formFactors: [ Widget.formfactor.mini ]
* });
*
* //resolves StringEditor
* fe.getDefaultConstructor('baja:String', { formFactors: [ 'mini' ] });
*
* //resolves undefined
* fe.getDefaultConstructor('baja:String', { formFactors: [ 'compact' ] });
*
* @example
* // Will return myModule's SpecialNumericEditor instead of the default webEditors:NumericEditor
* fe.getDefaultConstructor('baja:Double', { properties: { uxFieldEditor: 'myModule:SpecialNumericEditor' } });
*/
fe.getDefaultConstructor = function getDefaultConstructor(type, params) {
return deriveSpecifiedConstructor(params)
.then(function (Ed) {
return Ed || getReg().resolveFirst(type, toRegistryQuery(params));
});
};
/**
* Retrieve all available widget constructors for the given type.
*
* @param {String|Type} type
* @param {Object} [params]
* @param {Array.<String>} [params.formFactors]
* @returns {Promise.<Array.<Function>>} promise to be resolved with an array
* of constructor functions.
*/
fe.getConstructors = function getConstructors(type, params) {
return deriveSpecifiedConstructor(params)
.then(function (Ed) {
return Ed ? [ Ed ] : getReg().resolveAll(type, toRegistryQuery(params));
});
};
//TODO: pass in validator functions
//TODO: register JS validators on BIValidator?
/**
* Instantiate a new editor for a value of a particular Type.
*
* Note that you will receive a constructed instance of the editor, but
* it is uninitialized - calling `instantiate()` and `load()` is still your
* job. (See `buildFor`.)
*
* @param {module:nmodule/webEditors/rc/fe/fe~FeParams} params
*
* @returns {Promise.<module:bajaux/Widget>} promise to be resolved with an
* editor instance, or rejected if invalid parameters are given.
*
* @example
* <caption>Instantiate an editor for a baja value. Note that the workflow
* below is easily simplified by using fe.buildFor() instead.</caption>
*
* var myString = 'my string';
* fe.makeFor({
* value: myString
* properties: { multiLine: true }
* }).then(function (editor) {
* return editor.initialize($('#myStringEditorDiv'))
* .then(function () {
* return editor.load(myString);
* });
* });
*/
fe.makeFor = function makeFor(params) {
return mgr.makeFor(params);
};
/**
* Instantiates an editor as in `makeFor`, but with the added steps of
* initializing and loading the editor. When the promise resolves, the
* editor will be initialized within the DOM, and the passed value will have
* been loaded into the editor.
*
* @param {module:nmodule/webEditors/rc/fe/fe~FeParams} params
* @param {Object} [params.initializeParams] any additional parameters to be
* passed to the editor's `initialize` method
* @param {Object} [params.loadParams] any additional parameters to be
* passed to the editor's `load` method
* @param {module:bajaux/Widget} [ed] optionally,
* pass in an editor instance to just initialize and load that, skipping the
* `makeFor` step
* @returns {Promise.<module:bajaux/Widget>} promise to be resolved with the
* instance of the editor (fully initialized and loaded), or rejected if
* invalid parameters are given (including missing `dom` parameter).
*
* @example
* <caption>Build a raw editor for a String</caption>
* fe.buildFor({
* value: 'my string',
* properties: { multiLine: true },
* dom: $('#myStringEditorDiv')
* }).then(function (editor) {
* //editor is now fully initialized and loaded
* });
*
* @example
* <caption>Build an editor for a slot on a component</caption>
* var myComponent = baja.$('baja:Component', { mySlot: 'hello world' });
*
* fe.buildFor({
* complex: myComponent,
* slot: 'mySlot',
* properties: { multiLine: true },
* dom: ${'#myStringSlotEditorDiv')
* }).then(function (editor) {
* //editor is now fully initialized and loaded
*
* $('#saveButton').click(function () {
* editor.save().then(function () {
* alert('your changes are applied to the component');
* });
* });
* });
*
* @example
* <caption>Build a StringEditor even though you plan to load a different
* kind of value into it.</caption>
* fe.buildFor({
* type: StringEditor,
* value: 5,
* dom: $('#myStringEditorDiv')
* }).then(function (stringEditor) {
* //StringEditor better be able to load the value you specified,
* //or this will reject instead.
* });
*
* @example
* <caption>Example showing the effects of the uxFieldEditor facet
* (BFacets.UX_FIELD_EDITOR). By setting this slot facet to the type spec
* of a `BIJavaScript` implementation, you can force the usage of a
* particular field editor instead of relying on the agent registration.
* </caption>
* fe.buildFor({
* value: 5,
* dom: $('#myStringEditorDiv'),
* properties: { uxFieldEditor: 'webEditors:StringEditor' }
* }).then(function (stringEditor) {
* //uxFieldEditor facet enforced usage of StringEditor instead of the
* //default NumericEditor
* });
*/
fe.buildFor = function buildFor(params, ed) {
return mgr.buildFor(params, ed);
};
return fe;
});