/**
* @copyright 2016 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/**
* @module nmodule/webEditors/rc/fe/BaseWidget
*/
define([ 'bajaux/Properties',
'bajaux/Widget',
'jquery',
'Promise',
'underscore',
'nmodule/js/rc/asyncUtils/asyncUtils',
'nmodule/js/rc/switchboard/switchboard' ], function (
Properties,
Widget,
$,
Promise,
_,
asyncUtils,
switchboard) {
'use strict';
var doRequire = asyncUtils.doRequire,
SHOULD_VALIDATE = 'shouldValidate';
////////////////////////////////////////////////////////////////
// Support functions
////////////////////////////////////////////////////////////////
var requireFe = _.once(function () {
return doRequire('nmodule/webEditors/rc/fe/fe');
});
////////////////////////////////////////////////////////////////
// BaseWidget
////////////////////////////////////////////////////////////////
/**
* Base class for all `webEditors` widgets. This widget includes lots of
* sugar for the `Widget` constructor, utilities for managing nested
* collections of `Widget`s, etc.
*
* Note that this widget has no idea what BajaScript is; for more
* Baja-specific editor functionality, reach for `BaseEditor`.
*
* @class
* @extends module:bajaux/Widget
* @alias module:nmodule/webEditors/rc/fe/BaseWidget
* @mixes tinyevents
* @param {Object} [params]
* @param {module:bajaux/Properties|Object} [params.properties] properties to
* add to this editor's underlying `bajaux/Properties` instance. This can be
* either a `Properties` instance or an object literal.
* @param {Boolean} [params.enabled] false to disable this editor
* @param {Boolean} [params.readonly] true to readonly this editor
* @param {String} [params.formFactor] form factor this editor should use
* (c.f. `Widget.formfactor`)
* @param {String} [params.keyName] the key name that bajaux should use to
* look up lexicon entries for this editor
* @param {String} [params.moduleName='webEditors'] the module name that
* bajaux should use to look up lexicon entries for this editor
* @param {Object} [params.data] optional additional configuration data that
* may be used on a per-widget basis. This will often be used in conjunction
* with `fe`.
*/
var BaseWidget = function BaseWidget(params) {
Widget.call(this, _.extend({ moduleName: 'webEditors' }, params));
//TODO: destroy
//multiple calls to load() all take effect, but will pile up
switchboard(this, {
load: { allow: 'oneAtATime', onRepeat: 'queue' }
});
};
BaseWidget.prototype = Object.create(Widget.prototype);
BaseWidget.prototype.constructor = BaseWidget;
/**
* `VALUE_READY_EVENT` is a completely optional event. You may choose to
* trigger this event when the user has taken some action to indicate that
* the given value is the desired one, *without* actually saving the editor.
*
* Why would you want to trigger this event? Currently the best reason would
* be if your editor is shown in a dialog, and it has some kind of selection
* mechanism (dropdown, radio buttons, list etc.). This would allow the user
* to simply select a value and allow the dialog to close, without actually
* making the user click OK to save.
*
* For instance, `feDialogs` listens for `VALUE_READY_EVENT` when showing an
* editor in a dialog. When the event is emitted, the dialog will
* automatically be saved and closed.
*
* To illustrate, an OrdChooser in a dialog will show a station nav tree. When
* the user double-clicks a file, `VALUE_READY_EVENT` will be emitted.
* Therefore all the user must do is double-click the file to select it.
* Without the event, the user would have to click to select the file, then
* manually go down and click OK to commit the changes.
*
* You may also respond to this event when fired directly on the editor
* itself. Why would you do this? Certain framework modules may invert this
* pattern, that is, the framework may notify the editor itself that the user
* has chosen a value he or she is satisfied with, without saving the editor.
* An example of this is, again, `feDialogs`: when the user clicks OK, the
* current value will be read and used elsewhere, although the editor may not
* itself be saved.
*
* The two uses refer to the same use case (the user likes this value even
* without wishing to commit it to the station yet); they just go in two
* different directions: one is the editor notifying the framework, the other
* is the framework notifying the editor. Be careful not to re-trigger one in
* a handler for the other, or you may get an infinite loop!
*
* @see module:webEditors/rc/fe/feDialogs
* @type {string}
* @example
* dom.on('dblclick', '.listItem', function (e) {
* var value = $(this).text();
* dom.trigger(BaseWidget.VALUE_READY_EVENT, [ value ]);
* });
*
* @example
* function MyEditor() {
* ...
* var that = this;
* that.on(VALUE_READY_EVENT, function (newValue) {
* console.log('The user has chosen to commit this value: ' + newValue);
* //...even though it may not be written to the station yet.
* return that.saveToMostRecentlyUsedValues(newValue);
* });
* }
*/
BaseWidget.VALUE_READY_EVENT = 'BaseWidget:valueReady';
/**
* Every `BaseWidget` will add the `editor` class to the element and emit an
* `initialized` tinyevent when initialized.
*
* @param {JQuery} dom
* @returns {Promise} call to {@link module:bajaux/Widget#initialize}
*/
BaseWidget.prototype.initialize = function (dom) {
dom.addClass('editor');
return Widget.prototype.initialize.apply(this, arguments);
};
/**
* Removes the `editor` class and emits a `destroyed` tinyevent.
*
* @returns {Promise} call to {@link module:bajaux/Widget#destroy}
*/
BaseWidget.prototype.destroy = function () {
const jq = this.jq();
//TODO: select for bajaux-initialized kids and log warning if found
return Widget.prototype.destroy.apply(this, arguments)
.then(() => jq && jq.removeClass('editor'));
};
BaseWidget.prototype.makeChildFor = function (params) {
var that = this;
//avoid circular dependency
return requireFe()
.then(function (fe) {
return fe.makeFor(_.extend({
enabled: that.isEnabled(),
readonly: that.isReadonly()
}, params));
});
};
//TODO: revisit buildChildFor:
//assert child dom is child of my dom?
//listen for modified events - set self modified if child modified?
/**
* Builds a child editor to belong to this parent editor. Currently, this is
* only an easy way to have a child editor share its parent's enabled and
* readonly status. Consider this private API until better defined.
*
* @private
* @param params
* @returns {Promise} promise to be resolved with a child editor
* instance
* @see module:nmodule/webEditors/rc/fe/fe
*/
BaseWidget.prototype.buildChildFor = function (params) {
var that = this;
return that.makeChildFor(params)
.then(function (kid) {
return requireFe()
.then(function (fe) {
return fe.buildFor(params, kid);
});
});
};
/**
* String `Property` name used by `shouldValidate()`.
* @type {string}
*/
BaseWidget.SHOULD_VALIDATE = SHOULD_VALIDATE;
/**
* The `shouldValidate` `Property` should match this value if this editor
* should always validate on save.
* @type {number}
*/
BaseWidget.VALIDATE_ON_SAVE = 1;
/**
* The `shouldValidate` `Property` should match this value if this editor
* should always validate on read.
* @type {number}
*/
BaseWidget.VALIDATE_ON_READ = 2;
/**
* This provides an extra hook for an editor to declare itself as needing to
* be validated before saving or not. The default behavior is to return true
* if this editor is modified, or if a `shouldValidate` `bajaux` `Property`
* is present and truthy. If neither of these conditions is true, it will
* check all known child editors, and return true if it has a child editor
* that should validate.
*
* If `flag` is given, then the check against the `shouldValidate`
* `Property` will return true _only_ if the value bitwise matches the
* parameter. See `BaseWidget.SHOULD_VALIDATE_ON_SAVE`, etc.
*
* @param {Number} [flag]
*
* @returns {Boolean}
*/
BaseWidget.prototype.shouldValidate = function (flag) {
var prop = this.properties().getValue(SHOULD_VALIDATE);
if (prop !== null) {
if (typeof prop === 'boolean') {
return prop;
}
return flag ? !!(flag & prop) : true;
}
if (this.isModified()) {
return true;
}
return !!_.find(this.getChildWidgets({ type: BaseWidget }), function (kid) {
return kid.shouldValidate(flag);
});
};
return BaseWidget;
});