/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/* eslint-env browser */
/**
* @module bajaux/util/CommandButton
*/
define([ 'jquery',
'Promise',
'underscore',
'bajaux/events',
'bajaux/Widget',
'bajaux/commands/Command',
'bajaux/commands/ToggleCommand',
'bajaux/icon/iconUtils',
'bajaux/util/acceleratorUtils',
'nmodule/js/rc/log/Log' ], function (
$,
Promise,
_,
events,
Widget,
Command,
ToggleCommand,
iconUtils,
acceleratorUtils,
Log) {
'use strict';
const { contains, debounce, isFunction } = _;
const COMMAND_CHANGE_EVENT = events.command.CHANGE_EVENT;
const { DISABLE_EVENT, ENABLE_EVENT } = events;
const { toHtml } = iconUtils;
const logError = Log.logMessage.bind(Log,
'nmodule.bajaux.rc.util.CommandButton', Log.Level.SEVERE);
const FPS_60 = 1000 / 60;
let cmdButtonsWaiting = [];
const updateAll = debounce(function () {
cmdButtonsWaiting.forEach(function (button) {
button.$updateDom().catch(logError);
});
cmdButtonsWaiting = [];
}, FPS_60);
/**
* When enabling/disabling multiple buttons at once, don't throttle each call
* separately as they'll resolve at different times and give you multiple
* repaints. Throttle all commands at once to minimize repaints.
* @param {module:bajaux/util/CommandButton} cmdButton
*/
function updateCommandButtonDom(cmdButton) {
if (!contains(cmdButtonsWaiting, cmdButton)) {
cmdButtonsWaiting.push(cmdButton);
updateAll();
}
}
/**
* A widget for displaying and invoking a Command.
*
* @class
* @extends module:bajaux/Widget
* @alias module:bajaux/util/CommandButton
*/
const CommandButton = function CommandButton() {
Widget.apply(this, arguments);
};
CommandButton.prototype = Object.create(Widget.prototype);
CommandButton.prototype.constructor = CommandButton;
/**
* Get the element that will hold the icon `img`s.
*
* @private
* @returns {JQuery}
*/
CommandButton.prototype.$getIconElement = function () {
return this.jq().children('.display').children('.icon');
};
/**
* Get the element that will hold the command's display name.
*
* @private
* @returns {JQuery}
*/
CommandButton.prototype.$getDisplayNameElement = function () {
return this.jq().children('.display').children('.displayName');
};
/**
* Arms a click handler that will invoke the loaded Command. Adds a
* `CommandButton` CSS class.
*
* Technically, this widget can be initialized in any DOM element, but makes
* the most sense in a `button` element.
*
* @param {JQuery} dom
*/
CommandButton.prototype.doInitialize = function (dom) {
dom.on('click', (e) => {
const cmd = this.value();
Promise.resolve(this.doInvoke(cmd, e)).catch((err) => {
logError(err);
return cmd.defaultNotifyUser(err);
});
});
const el = dom[0];
if (!el) { return; }
el.classList.add('CommandButton');
const displaySpan = document.createElement('span');
displaySpan.className = 'display';
const iconSpan = document.createElement('span');
iconSpan.className = 'icon';
const displayNameSpan = document.createElement('span');
displayNameSpan.className = 'displayName';
displaySpan.appendChild(iconSpan);
displaySpan.appendChild(displayNameSpan);
el.append(displaySpan);
};
/**
* Override point to allow customized command invocation from a DOM event. By
* default, will check that the widget and command are both enabled and then
* invoke it.
*
* @param {module:bajaux/commands/Command} cmd
* @param {JQuery.TriggeredEvent} e
* @returns {Promise|undefined}
* @since Niagara 4.11
*/
CommandButton.prototype.doInvoke = function (cmd, e) {
if (!cmd.isEnabled() || !this.isEnabled()) { return; }
if (isFunction(cmd.invokeFromEvent)) {
return cmd.invokeFromEvent(e);
}
return cmd.invoke();
};
/**
* Updates the display name, description, icon, and enabled status of the
* button widget. Should be called once on load and whenever the loaded
* Command changes.
*
* @private
* @returns {Promise} promise to be resolved when the DOM has finished
* updating
*/
CommandButton.prototype.$updateDom = function () {
const that = this,
jq = that.jq(),
cmd = that.value();
if (!jq || !jq.length) {
return Promise.resolve();
}
return Promise.all([ cmd.toDisplayName(), cmd.toDescription() ])
.then(([ displayName, description ]) => {
if (!that.isInitialized()) { return; }
const iconElement = that.$getIconElement(),
displayNameElement = that.$getDisplayNameElement(),
isToggled = cmd.isToggleCommand() && cmd.isSelected(),
icon = cmd.getIcon(),
canInvoke = that.canInvokeCommand();
let title = description;
if (!title || (displayName.startsWith(description) && description.length < displayName.length)) {
title = displayName;
} else if (displayName && !(description.startsWith(displayName) || displayName.startsWith(description)) && displayNameElement.css('display') === 'none') {
title = displayName + "\n" + description;
}
if (acceleratorUtils.isProfileCommandGroup(jq) && cmd.getAccelerator()) {
title += "\n(" + cmd.getAccelerator() + ")";
}
jq.attr('title', title);
displayNameElement.text(displayName);
jq.toggleClass('ux-toggled', isToggled);
that.$lookEnabled(canInvoke);
that.trigger(canInvoke ? ENABLE_EVENT : DISABLE_EVENT);
return icon && toHtml(icon).then((html) => iconElement.html(html));
});
};
/**
* Loads a Command. Binds an event handler to update the DOM whenever the
* Command's properties are changed.
*
* @param {module:bajaux/commands/Command} cmd
* @returns {Promise} promise to be resolved when the `Command` is
* loaded, or rejected if no `Command` given
*/
CommandButton.prototype.doLoad = function (cmd) {
if (!(cmd instanceof Command)) {
throw new Error('Command required');
}
const that = this,
changeHandler = that.$changeHandler;
cmd.loading().then(function () {
if (changeHandler) {
cmd.off(COMMAND_CHANGE_EVENT, changeHandler);
}
cmd.on(COMMAND_CHANGE_EVENT, that.$changeHandler = function () {
updateCommandButtonDom(that);
});
})
.catch(logError);
return that.$updateDom();
};
/**
* Removes the click handler and CSS class from `doInitialize`.
*/
CommandButton.prototype.doDestroy = function () {
this.jq()
.removeClass('CommandButton')
.removeClass('ux-disabled');
var cmd = this.value();
if (cmd) {
cmd.off(COMMAND_CHANGE_EVENT, this.$changeHandler);
}
};
/**
* Updates the DOM to look enabled/disabled depending on whether this widget
* is enabled and the loaded command can be invoked.
*/
CommandButton.prototype.doEnabled = function () {
this.$lookEnabled(this.canInvokeCommand());
};
/**
* @returns {boolean} true if both the CommandButton and the loaded Command
* are enabled
* @since Niagara 4.13
*/
CommandButton.prototype.canInvokeCommand = function () {
const cmd = this.value();
return !!cmd && this.isEnabled() && cmd.isEnabled();
};
/**
* Updates the DOM to reflect whether the loaded Command can be invoked
* @private
* @param {boolean} enabled
*/
CommandButton.prototype.$lookEnabled = function (enabled) {
this.jq().prop('disabled', !enabled).toggleClass('bajaux-disabled ux-disabled', !enabled);
};
return CommandButton;
});