/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/*jshint browser: true */
/*global define */
define("dialogs", [
"jquery",
"Promise",
"underscore",
"lex!js",
"hbs!nmodule/js/rc/dialogs/dialogs",
"hbs!nmodule/js/rc/dialogs/loader",
"css!nmodule/js/rc/dialogs/dialogs"], function (
$,
Promise,
_,
lexicons,
template,
loaderTemplate) {
"use strict";
var defaultLoadImg = loaderTemplate(),
exports,
lex = lexicons[0],
openDialogs = [],
resizeHandler,
unknownErr = 'Unknown error';
/**
* This is a convenience method used for working with functions that take
* an Object Literal as an argument.
*
* This method always ensures an Object is returned so its properties can be
* further validated.
*
* @inner
*/
function objectify(obj, propName) {
if (!(obj === undefined || obj === null)) {
if (obj.constructor === Object) {
return obj;
}
else if (typeof propName === "string") {
var o = {};
o[propName] = obj;
return o;
}
}
return {};
}
/**
* A default button handler that returns a resolved promise.
*
* @inner
*
* @returns {Promise}
*/
function defaultButtonHandler() {
return Promise.resolve();
}
/**
* Define a default value for possibly undefined variables.
*
* @inner
*
* @param val The value to be tested.
* @param defVal The default value to be returned if the value is undefined.
* @returns The default value if the value is undefined.
*/
function def(val, defVal) {
return val === undefined ? defVal : val;
}
////////////////////////////////////////////////////////////////
// Button DOM
////////////////////////////////////////////////////////////////
/**
* Return true if the Button's DOM Element is in a state
* where it can be clicked (i.e. it's not hidden or disabled).
*
* @inner
*
* @param {jQuery} buttonJq
* @return {Boolean} true if the button can be clicked.
*/
function canClick(buttonJq) {
return buttonJq &&
!$("button", buttonJq).attr("disabled") &&
buttonJq.css("display") !== "none";
}
/**
* Set the button to be enabled/disabled.
*
* @inner
*
* @param {jQuery} buttonJq The DOM for the button.
* @param {Boolean} enable
*/
function setButtonEnable(buttonJq, enable) {
var b = $("button", buttonJq);
if (enable) {
b.removeAttr("disabled");
}
else {
b.attr("disabled", "disabled");
}
}
/**
* Show or hide a button.
*
* @inner
*
* @param {jQuery} buttonJq The button's DOM.
* @param {boolean} show
*/
function setButtonShow(buttonJq, show) {
if (show) {
buttonJq.show();
}
else {
buttonJq.hide();
}
}
/**
* Set the data on the button's DOM.
*
* @inner
*
* @param {jQuery} buttonJq The button's DOM.
* @param {Object} data
*/
function setButtonData(buttonJq, data) {
$("button", buttonJq).data("js-dialog-button", data);
}
/**
* Return the button object for the button name.
*
* @inner
*
* @param {Dialog} dlg The Dialog instance.
* @param {String} name The name of the button.
* @return {Object} the button data.
*/
function findButton(dlg, name) {
var buttons = dlg.$params.buttons,
i;
for (i = 0; i < buttons.length; ++i) {
if (buttons[i].name === name) {
return buttons[i];
}
}
throw new Error("Unable to find dialog button: " + name);
}
/**
* Find the button DOM under the dialog's DOM via its name.
*
* @inner
*
* @param {jQuery} dialogJq The Dialog's DOM object.
* @param {String} name The name of the button to look up.
* @return {jQuery} The button's DOM object.
*/
function findButtonDom(dialogJq, name) {
return $(".js-dialog-button-" + name, dialogJq).parent();
}
////////////////////////////////////////////////////////////////
// DOM Creation
////////////////////////////////////////////////////////////////
/**
* Initialize the DOM for the button.
*
* @inner
*
* @param {Dialog} dlg
* @param {jQuery} buttonJq
* @param {Object} buttonData
*/
function initButtonDom(dlg, buttonJq, buttonData) {
var name = buttonData.name,
displayName = buttonData.displayName || name,
handlerArray = buttonData.handlerArray,
hide = buttonData.hide,
disable = buttonData.disable;
buttonData.jq = buttonJq;
// Ensure the button name is always defined.
if (typeof name !== "string") {
throw new Error("Dialog button has no name!");
}
buttonJq.click(function () {
var args = [dlg].concat(Array.prototype.slice.call(arguments)),
i,
promises = [],
retVal;
if (!canClick(buttonJq)) {
return;
}
try {
for (i = 0; i < handlerArray.length; ++i) {
try {
retVal = handlerArray[i].apply(this, args);
} catch (e) {
return dlg.close(name, /*fail*/ e || new Error());
}
// If a handler returns false then this means we need to use a
// rejected promise.
if (retVal === false) {
retVal = Promise.reject();
}
// When the button is clicked invoke the handler and hold any promises returned
promises.push(retVal);
}
// Close the dialog once all the promises have been resolved
Promise.all(promises)
.then(function () {
dlg.close(name);
})
.catch(_.noop);
}
finally {
$(window).trigger("jsdialog:buttonpressed",
[dlg, name, displayName, handlerArray]);
}
});
if (disable) {
setButtonEnable(buttonJq, /*enable*/false);
}
if (hide) {
setButtonShow(buttonJq, /*show*/false);
}
setButtonData(buttonJq, buttonData);
}
/**
* Returns a deferred promise object that has properties for
* the resolve and reject methods as well as a property for the original
* promise reference.
*
* @inner
*
* @return {Object} An object that has resolve, reject and promise properties.
*/
function deferred() {
var resolve = null,
reject = null,
promise = new Promise(function (res, rej) {
resolve = res;
reject = rej;
});
return {
resolve: resolve,
reject: reject,
promise: promise
};
}
/**
* Initialize the DOM for the Dialog box.
*
* @inner
*
* @param {Dialog} dlg
*/
function initDom(dlg) {
var dialogJq,
params = dlg.$params,
buttons = params.buttons,
i,
bt,
templateData = {
title: params.title,
content: typeof params.content === "function" ? "<span></span>" : params.content,
loading: params.loading,
buttons: buttons,
fade: params.fade
};
if (params.cancel) {
buttons.push({
name: "cancel",
displayName: lex.get("dialogs.cancel"),
handler: params.cancel,
esc: true
});
}
if (params.no) {
buttons.unshift({
name: "no",
displayName: lex.get("dialogs.no"),
handler: params.no
});
}
if (params.yes) {
buttons.unshift({
name: "yes",
displayName: lex.get("dialogs.yes"),
handler: params.yes
});
}
if (params.ok) {
buttons.unshift({
name: "ok",
displayName: lex.get("dialogs.ok"),
handler: params.ok
});
}
dialogJq = $(template(templateData));
findMainDialogElement(dialogJq).data("js-dialog", dlg);
// Initialize the buttons
for (i = 0; i < buttons.length; ++i) {
bt = buttons[i];
bt.handlerArray = [bt.handler || defaultButtonHandler];
initButtonDom(dlg,
findButtonDom(dialogJq, bt.name),
bt);
}
dlg.$dialogJq = dialogJq;
}
function findMainDialogElement(dialogJq) {
return dialogJq.children('.js-dialog');
}
function findContentWrapperElement(dialogJq) {
return findMainDialogElement(dialogJq).children('.js-dialog-content-wrapper');
}
function findContentElement(dialogJq) {
return findContentWrapperElement(dialogJq).children('.js-dialog-content');
}
function findButtonsElement(dialogJq) {
return findMainDialogElement(dialogJq).children('.js-dialog-button-content');
}
function findHeaderElement(dialogJq) {
return findMainDialogElement(dialogJq).children('.js-dialog-header');
}
////////////////////////////////////////////////////////////////
// Dialog
////////////////////////////////////////////////////////////////
/**
* A class for a Dialog box.
*
* An instance of a Dialog can be accessed indirectly by use of
* one of the showXxx methods.
*
* @class
* @inner
* @public
* @memberOf module:dialogs
*
* @example
* <caption>Show a basic simple OK Dialog box</caption>
* dialogs.showOk("Here's a nice OK dialog box!");
*/
var Dialog = function Dialog(params) {
var that = this;
params = objectify(params, "content");
// Set up default dialog parameters
params.title = def(params.title, "");
params.content = def(params.content, "");
params.buttonNames = params.buttonNames || {};
params.buttons = params.buttons || [];
params.parent = params.parent || $("body");
params.private = params.private || false;
that.$params = params;
that.$closed = false;
that.$hidden = false;
that.$dialogJq = null;
// Inner deferred promise. This promise will be resolved
// when the dialog is closed.
that.$df = deferred();
// Create the DOM but don't attach it.
initDom(that);
};
function cancelDelayId(dlg) {
if (dlg.$delayId) {
clearTimeout(dlg.$delayId);
}
}
/**
* When a dialog is shown or the window is resized, the content wrapper
* element needs to have its max-height set to prevent the dialog buttons
* from being scrolled off the bottom of the page.
*
* @inner
* @param {Dialog} dlg
*/
function layout(dlg) {
if (!resizeHandler) {
resizeHandler = _.throttle(function () {
exports.each(function (i, dlg) { layout(dlg); });
}, 1000 / 60);
$(window).on('resize', resizeHandler);
}
var dialogJq = dlg.jq(),
wrapper = findContentWrapperElement(dialogJq),
mainElement = findMainDialogElement(dialogJq),
header = findHeaderElement(dialogJq),
buttons = findButtonsElement(dialogJq),
// getComputedStyle excludes borders and padding
containerHeight = window.getComputedStyle(dialogJq[0], null).getPropertyValue('height'),
// container will be taller than dialog when enlarging window, vice versa when shrinking
maxHeight = Math.max(mainElement.innerHeight(), parseInt(containerHeight, 10)),
headerHeight = header.outerHeight() || 0,
buttonsHeight = buttons.outerHeight() || 0,
height = maxHeight - (headerHeight + buttonsHeight);
wrapper.css('max-height', height);
var layoutParam = dlg.$params.layout;
if (typeof layoutParam === 'function') {
layoutParam(dlg);
}
}
/**
* Show the Dialog.
*
* @returns {module:dialogs~Dialog}
*
* @example
* <caption>Create a blank dialog box and then show it.</caption>
* dialogs.make("A dialog box with no buttons!")
* .show();
*/
Dialog.prototype.show = function show() {
var that = this,
dialogJq = that.$dialogJq,
webDlg;
cancelDelayId(that);
// Ensure the dialog's first button has focus.
function autoFocus() {
$(".js-dialog-button-content button:first", webDlg).focus();
}
//Sets the background opacity to screen the contents when the dialog is showing
function setPrivate() {
if (that.$params.private) {
dialogJq.addClass("js-dialog-container-private");
}
}
if (!that.$closed) {
// Lazily attach the dialog to the DOM
if (dialogJq.parent().length === 0) {
openDialogs.push(that);
that.$params.parent.append(dialogJq);
$(window).trigger("jsdialog:created", [that]);
}
that.$hidden = false;
if (that.$params.fade) {
dialogJq.stop(/*clearQueue*/true, /*jumpToEnd*/true);
dialogJq.fadeIn("fast", autoFocus);
} else {
dialogJq.show();
setPrivate();
autoFocus();
}
layout(that);
}
$(window).trigger("jsdialog:show", [that]);
return that;
};
/**
* Hide the Dialog without closing it. The preferred method
* to use is close.
*
* @returns {module:dialogs~Dialog}
*
* @example
* <caption>
* Hide a dialog box after 2 seconds
* </caption>
* dialogs.showYesNo("Meeting alert! Do you want to be reminded in 10 seconds?")
* .yes(function (dialog) {
* dialog.hide();
* setTimeout(function () {
* dialog.show();
* }, 10000);
* return false;
* });
*/
Dialog.prototype.hide = function hide() {
var that = this,
dialogJq = that.$dialogJq;
cancelDelayId(that);
if (!that.$closed) {
that.$hidden = true;
if (that.$params.fade) {
dialogJq.stop(/*clearQueue*/true, /*jumpToEnd*/true);
dialogJq.fadeOut("fast");
}
else {
dialogJq.hide();
}
$(window).trigger("jsdialog:hide", [that]);
}
return that;
};
function removeFromOpenDialogs(dlg) {
// Remove the dialog from the array
var i;
for (i = 0; i < openDialogs.length; ++i) {
if (openDialogs[i] === dlg) {
openDialogs.splice(i, 1);
break;
}
}
}
/**
* Close the Dialog. This will remove the Dialog box from the screen.
*
* @param {String} [name] The name of the button used to close the dialog box.
* This parameter is designed to be called from the Dialog JS framework itself.
* @param {*} [fail] optional failure reason. If truthy, the dialog's promise
* will be rejected with this failure reason; otherwise, the promise will be
* resolved.
* @returns {module:dialogs~Dialog}
*
* @example
* <caption>
* Open a Dialog and close it after 2 seconds
* </caption>
* var dlg = dialogs.showOk("A notification");
*
* setTimeout(function () {
* dlg.close();
* }, 2000);
*/
Dialog.prototype.close = function close(name, fail) {
var that = this,
dialogJq = that.$dialogJq;
name = name || "cancel";
that.$closed = true;
cancelDelayId(that);
if (dialogJq) {
dialogJq.stop(/*clearQueue*/true, /*jumpToEnd*/true);
if (that.$params.fade) {
dialogJq.fadeOut("fast", function () {
dialogJq.remove();
});
} else {
dialogJq.remove();
}
removeFromOpenDialogs(that);
$(window).trigger("jsdialog:close", [that]);
}
// Once closed, resolve the promise with the specified name
if (!fail) {
that.$df.resolve([that, name]);
}
else {
that.$df.reject([that, name, fail]);
}
return that;
};
/**
* Move the Dialog to the front.
*
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.toFront = function toFront() {
var that = this,
dialogJq = that.$dialogJq;
if (dialogJq && !that.$closed && openDialogs.length > 1) {
findMainDialogElement(dialogJq).stop(/*clearQueue*/true, /*jumpToEnd*/true);
removeFromOpenDialogs(that);
openDialogs.push(that);
that.$params.parent.append(dialogJq);
}
return that;
};
/**
* Move the Dialog to the back.
*
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.toBack = function toBack() {
var that = this,
dialogJq = that.$dialogJq;
if (dialogJq &&
!that.$closed &&
openDialogs.length > 1 &&
openDialogs[1].$dialogJq) {
findMainDialogElement(dialogJq).stop(/*clearQueue*/true, /*jumpToEnd*/true);
removeFromOpenDialogs(that);
openDialogs.unshift(that);
dialogJq.insertBefore(openDialogs[1].$dialogJq);
}
return that;
};
/**
* Return true if the Dialog is closed and removed from the DOM.
*
* @returns {Boolean} Return true if the Dialog has been closed.
*/
Dialog.prototype.isClosed = function isClosed() {
return this.$closed;
};
/**
* Return true if the Dialog is hidden.
*
* @returns {Dialog} Return true if the Dialog has been hidden.
*/
Dialog.prototype.isHidden = function isHidden() {
return this.$hidden;
};
/**
* Add a callback handler for a button via its name. This callback
* handler will be invoked when the button is clicked.
*
* Any handler function can return a jQuery Deferred Promise. This
* can control when and if the Dialog box closes after the handler
* has been invoked. It should be noted that multiple handlers
* can be registered on a button.
*
* * If the handlers return nothing, the Dialog will be closed after
* all the Handlers have been invoked.
* * If one or more handlers return a jQuery Deferred Promise, the Dialog
* will only close after all the jQuery Promises have been resolved.
* * If one of the Deferred Promises is rejected, the Dialog will not close.
*
* @param {String} name The name of the button to register the handler on.
* @param {Function} handler The handler of the function to be
* invoked when the button is clicked. When invoked, the first argument of the
* handler is the Dialog instance.
* @returns {module:dialogs~Dialog}
*
* @see module:dialogs~Dialog#ok
* @see module:dialogs~Dialog#cancel
* @see module:dialogs~Dialog#yes
* @see module:dialogs~Dialog#no
*
* @example
* <caption>
* Register a function be to be called when the 'foo' button
* is clicked.
* </caption>
* dialogs.show({
* content: "Show some stuff",
* buttons: [
* name: "foo",
* handler: function () {
* alert("First annoying alert!");
* }
* ]
* }).on("foo", function () {
* alert("This will also be called when foo button is clicked.");
* });
*/
Dialog.prototype.on = function on(name, handler) {
findButton(this, name).handlerArray.push(handler);
return this;
};
/**
* Click one of the Dialog's buttons.
*
* @example
* <caption>
* Show a Dialog with an OK button and click it 2 seconds later
* </caption>
* var dlg = dialogs.showOk("This is an OK Dialog")
* setTimeout(function () {
* dlg.click("ok")
* }, 2000);
*
* @param {String} name The name of the Dialog button to click.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.click = function click(name) {
var bt = findButton(this, name);
if (canClick(bt.jq)) {
bt.jq.click();
}
return this;
};
function onButton(dlg, name, handler) {
if (handler) {
return dlg.on(name, handler);
}
else {
return dlg.click(name);
}
}
/**
* Add a 'ok' handler to the Dialog or if no handler is specified,
* simulate clicking the Dialog's 'ok' button.
*
* @see module:dialogs~Dialog#on
*
* @param {Function} [handler] if specified, the handler to be invoked
* when the Dialog's 'ok' button is clicked. The first argument of the
* function callback is the Dialog instance.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.ok = function ok(handler) {
return onButton(this, "ok", handler);
};
/**
* Add a 'cancel' handler to the Dialog or if no handler is specified,
* simulate clicking the Dialog's 'cancel' button.
*
* @see module:dialogs~Dialog#on
*
* @param {Function} [handler] if specified, the handler to be invoked
* when the Dialog's 'cancel' button is clicked. The first argument of the
* function callback is the Dialog instance.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.cancel = function cancel(handler) {
return onButton(this, "cancel", handler);
};
/**
* Add a 'yes' handler to the Dialog or if no handler is specified,
* simulate clicking the Dialog's 'yes' button.
*
* @see module:dialogs~Dialog#on
*
* @param {Function} [handler] if specified, the handler to be invoked
* when the Dialog's 'yes' button is clicked. The first argument of the
* function callback is the Dialog instance.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.yes = function yes(handler) {
return onButton(this, "yes", handler);
};
/**
* Add a 'no' handler to the Dialog or if no handler is specified,
* simulate clicking the Dialog's 'no' button. The first argument of the
* function callback is the Dialog instance.
*
* @see module:dialogs~Dialog#on
*
* @param {Function} [handler] if specified, the handler to be invoked
* when the Dialog's 'no' button is clicked.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.no = function no(handler) {
return onButton(this, "no", handler);
};
/**
* Return the button DOM for the given name.
*
* @param {String} name The name of the button.
* @returns {jQuery} the Button's jQuery DOM object or null if nothing found.
*/
Dialog.prototype.buttonJq = function buttonJq(name) {
return findButton(this, name).jq;
};
/**
* Disable a button.
*
* @param {String} name The name of the button.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.disableButton = function disableButton(name) {
setButtonEnable(this.buttonJq(name), /*enable*/false);
return this;
};
/**
* Enable a button.
*
* @param {String} name The name of the button.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.enableButton = function enableButton(name) {
setButtonEnable(this.buttonJq(name), /*enable*/true);
return this;
};
/**
* Show a button.
*
* @param {String} name The name of the button.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.showButton = function showButton(name) {
setButtonShow(this.buttonJq(name), /*show*/true);
return this;
};
/**
* Hide a button.
*
* @param {String} name The name of the button.
* @returns {module:dialogs~Dialog}
*/
Dialog.prototype.hideButton = function hideButton(name) {
setButtonShow(this.buttonJq(name), /*show*/false);
return this;
};
/**
* Return the internal jQuery wrapped DOM element for the entire Dialog.
*
* @returns {jQuery} the Dialog's jQuery DOM object.
*/
Dialog.prototype.jq = function jq() {
return this.$dialogJq;
};
/**
* If this dialog has content, return the jQuery DOM wrapper
* for the Content.
*
* @returns {jQuery} The DOM wrapper for the content. This wrapper will
* be empty if the dialog is shown with no content.
*/
Dialog.prototype.content = function () {
return findContentElement(this.$dialogJq);
};
/**
* Return a promise for the dialog that will be resolved when the dialog closes.
* This is useful when wanting to use dialogs in a promise chain when creating a user interface.
*
* @returns {Promise} The promise to be resolved.
*/
Dialog.prototype.promise = function () {
return this.$df.promise;
};
/**
* An Object that defines a Button.
*
* @typedef {Object} module:dialogs~Button
* @inner
* @public
*
* @see module:dialogs~Dialog#on
*
* @property {String} [name] A button's unique name so it can be identified.
* @property {String} [displayName] The button's display name. This name will used
* on the button. If not specified, the button's name will be used instead.
* @property {Function} [handler] A function that will be invoked when the button is
* clicked. Returning false will keep the dialog from closing after this handler
* has been invoked.
* @property {Boolean} [esc] If true, this handler will be invoked when the user
* hits the escape key.
*/
////////////////////////////////////////////////////////////////
// Exports
////////////////////////////////////////////////////////////////
/**
* Create stunning, modal Dialog boxes in JavaScript.
*
* This is a UI library used to create dynamic, modal Dialog boxes in your
* browser.
*
* @example
* <caption>
* A simple modal OK dialog box.
* </caption>
* dialogs.showOk("Some Dialog Box Content")
* .ok(function () {
* console.log("The OK button has been clicked");
* });
*
* @example
* <caption>
* A Dialog box with a title and some HTML content.
* </caption>
* dialogs.showOk({
* title: "The Dialog Box's Title!",
* content: "<p>Some HTML Content</p>"
* })
* .ok(function () {
* console.log("The OK button has been clicked");
* });
*
* @example
* <caption>
* A simple Yes, No, Cancel dialog box.
* </caption>
* dialogs.showYesNoCancel("Would you like some tea with that?")
* .yes(function () {
* console.log("The user clicked Yes");
* })
* .no(function() {
* console.log("The user clicked No");
* })
* .cancel(function () {
* console.log("The user clicked Cancel");
* });
*
* @example
* <caption>
* Show a loading dialog box and have it close after the AJAX call has finished.
* </caption>
* dialogs.showLoading(0, $.ajax(uri, options));
*
* @example
* <caption>
* Use promises to show a loading dialog box and then pop up another dialog.
* </caption>
* var dlg = dialogs.showLoading();
* // After 2 seconds, close the loading box.
* setTimeout(function () {
* dlg.close();
* }, 2000);
* dlg.promise().spread(function (dlg, buttonClicked) {
* // Prints 'ok'
* console.log(buttonClicked);
*
* dialogs.showOk("The foobar has finished loading!");
* });
*
* @example
* <caption>
* Show a dialog. Have the content dynamically created by
* passing in a function for the content.
* </caption>
* dialogs.show(function(dlg, jq) {
* jq.html("<div>I love Niagara 4!</div>");
* });
*
* @example
* <caption>
* Show a dialog. Have the content dynamically created
* by passing in a function for the content. The dialog
* will only show when the return promise has been resolved.
* </caption>
* dialogs.show(function(dlg, jq) {
* return Promise.resolve($.ajax("/myajax")
* .then(function (response) {
* jq.html("The answer is..." + JSON.parse(response).answer);
* });
* });
*
* @example
* <caption>
* A Dialog BOX with background privacy setting.
* </caption>
* dialogs.showOk({
* title: "The Dialog Box's Title!",
* content: "<p>Some HTML Content</p>",
* private: true //ensures background contents are screened when the dialog is showing
* })
* .ok(function () {
* console.log("The OK button has been clicked");
* });
*
*
* @module dialogs
* @requires jquery
* @requires lex!js
* @requires css!nmodule/js/rc/dialogs/dialogs
*/
exports = {
/**
* Create a Dialog box and return it.
*
* Please note, this will not show the Dialog box but just return
* an instance of a new one.
*
* This method can take either an Object Literal for parameters or a
* singular String argument for the Dialog's Content.
*
* @see module:dialogs~Button
*
* @param {Object|String|Function} params parameters for launching a Dialog,
* or directly, the `content` to be shown.
* @param {String|Function} [params.content] the Dialog's content.
* Please note, this can be HTML. This can also be a function used to
* generate the content dynamically. The callback function is passed the
* Dialog instance and content jQuery element as parameters. The callback
* can return a promise that when resolved will show the dialog box. By
* default, if a promise is returned, a loading dialog box will appear.
* @param {String} [params.title] the Dialog's title. This should not contain HTML as any HTML will be escaped.
* @param {Boolean} [params.fade] if true, the Dialog box will fade in
* quickly.
* @param {Function} [params.layout] an optional callback function to be
* called when the dialog lays itself out. It will receive the
* {@link module:dialogs~Dialog dialog} instance as the first parameter.
* If your content needs to perform some logic to lay itself out when the
* dialog changes dimensions, use this callback to do so.
* @param {Function} [params.ok] handler to be invoked when the 'OK' button
* is clicked. By defining this handler, this will also cause an 'OK' button
* to be added to the Dialog box.
* @param {Function} [params.cancel] handler to be invoked when the 'Cancel'
* button is clicked. By defining this handler, this will also cause a
* 'Cancel' button to be added to the Dialog box.
* @param {Function} [params.yes] handler to be invoked when the 'Yes'
* button is clicked. By defining this handler, this will also cause a 'Yes'
* button to be added to the Dialog box.
* @param {Function} [params.no] handler to be invoked when the 'No' button
* is clicked. By defining this handler, this will also cause a 'No' button
* to be added to the Dialog box.
* @param {Array.<module:dialogs~Button>} [params.buttons] an array of additional
* buttons.
* @param {jQuery} [params.parent] A parent jQuery wrapped DOM element to
* attach the dialog to. If not specified, the dialog is attached to the
* HTML document's body element.
* @param {Boolean} [params.private] Defaults to false. If true, the background
* contents are not visible when the dialog is showing.
* @returns {module:dialogs~Dialog}
*
* @example
* <caption>Make a dialog box and show it</caption>
* dialogs.make("A dialog with no buttons")
* .show();
*/
make: function make(params) {
return new Dialog(params);
},
/**
* Create and show a Dialog box. Shortcut for `dialogs.make(params).show()`.
*
* @see module:dialogs.make
*
* @param {Object} params parameters for launching a Dialog. See
* {@link module:dialogs.make}.
*
* @returns {module:dialogs~Dialog}
*
* @example
* <caption>
* Show a simple Dialog box.
* </caption>
* dialogs.show({
* title: "Dialog",
* content: "Hey this is a Dialog!",
* ok: function () {
* console.log("The OK button has been clicked");
* }
* });
*
* dialogs.show(function(dlg, jq) {
* jq.html("<div>I love Niagara 4!</div>");
* });
*
* dialogs.show(function(dlg, jq) {
* return Promise.resolve($.ajax("/myajax")
* .then(function (response) {
* jq.html("The answer is..." + JSON.parse(response).answer);
* });
* });
*/
show: function show(params) {
params = objectify(params, "content");
var func,
loading = false,
dlg,
promise;
if (typeof params.content === "function") {
func = params.content;
// When a callback is used to generate the content, the loading dialog is on
// by default if the callback returns a promise.
loading = params.loading || params.loading === undefined;
delete params.loading;
}
dlg = exports.make(params);
if (func) {
promise = func(dlg, dlg.content());
// If the callback function returns a promise then
// make sure the dialog is shown when it resolves.
if (promise && typeof promise.then === "function") {
promise.then(function () {
dlg.show();
});
// If a promise is returned and we can show the loading dialog
// then show it.
if (loading) {
exports.showLoading(params.delay || 0, promise);
}
}
else {
dlg.show();
}
}
else {
dlg.show();
}
return dlg;
},
/**
* Iterate through each Dialog box.
*
* @param {Function} func called for each Dialog box whereby
* the first argument is index of the Dialog box with the second
* being the Dialog instance.
*/
each: function each(func) {
$.each(openDialogs.slice(0), func);
},
/**
* Return the number of Open Dialogs (includes hidden).
*
* @return {Number} the number of open Dialogs.
*/
size: function size() {
return openDialogs.length;
},
/**
* Creates and shows a Dialog box with an OK button.
*
* @param {Object} params parameters for launching a Dialog. See
* {@link module:dialogs.make}.
*
* @see module:dialogs.make
* @returns {module:dialogs~Dialog}
*/
showOk: function showOk(params) {
params = objectify(params, "content");
params.ok = params.ok || defaultButtonHandler;
return exports.show(params);
},
/**
* Creates and shows a Dialog box with OK and Cancel buttons.
*
* @param {Object} params parameters for launching a Dialog. See
* {@link module:dialogs.make}.
*
* @see module:dialogs.make
* @returns {module:dialogs~Dialog}
*/
showOkCancel: function showOkCancel(params) {
params = objectify(params, "content");
params.ok = params.ok || defaultButtonHandler;
params.cancel = params.cancel || defaultButtonHandler;
return exports.show(params);
},
/**
* Creates and shows a Dialog box with a Cancel button.
*
* @param {Object} params parameters for launching a Dialog. See
* {@link module:dialogs.make}.
*
* @see module:dialogs.make
* @returns {module:dialogs~Dialog}
*/
showCancel: function showCancel(params) {
params = objectify(params, "content");
params.cancel = params.cancel || defaultButtonHandler;
return exports.show(params);
},
/**
* Creates and shows a Dialog box with Yes and No buttons.
*
* @param {Object} params parameters for launching a Dialog. See
* {@link module:dialogs.make}.
*
* @see module:dialogs.make
* @returns {module:dialogs~Dialog}
*/
showYesNo: function showYesNo(params) {
params = objectify(params, "content");
params.yes = params.yes || defaultButtonHandler;
params.no = params.no || defaultButtonHandler;
return exports.show(params);
},
/**
* Creates and shows a Dialog box with Yes, No and Cancel buttons.
*
* @param {Object} params parameters for launching a Dialog. See
* {@link module:dialogs.make}.
*
* @see module:dialogs.make
* @returns {module:dialogs~Dialog}
*/
showYesNoCancel: function showYesNoCancel(params) {
params = objectify(params, "content");
params.yes = params.yes || defaultButtonHandler;
params.no = params.no || defaultButtonHandler;
params.cancel = params.cancel || defaultButtonHandler;
return exports.show(params);
},
/**
* Creates and shows a Loading Dialog box.
*
* @param {Number} [delay] An optional delay (in milliseconds) before the
* Dialog box appears. By default, there is no delay in showing the Dialog.
* @param {Promise} [promise] An optional Promise that can be passed
* into the loading Dialog box. If defined, the loading Dialog box will
* automatically close after the promise has completed.
* @param {jQuery} [parent] The DOM parent to attach the dialog too. If not specified,
* the dialog is attached to the HTML page's body.
* @param {String} [loaderImg] an alternate loader image to use.
* @returns {module:dialogs~Dialog}
*
* @example
* <caption>
* Show a loading Dialog box if an AJAX call takes longer than
* half a second to complete.
* </caption>
*
* // Make an AJAX call that may take longer that half a second.
* var promise = $.ajax({
* url: "test.html",
* context: document.body
* }).done(function() {
* $(this).addClass("done");
* });
*
* // If the AJAX call takes longer that half a second, display a
* // Loading Dialog box.
* dialogs.showLoading(500, promise);
*/
showLoading: function showLoading(delay, promise, parent, loaderImg) {
loaderImg = loaderImg || defaultLoadImg;
// Ensure the image is loaded before we display the loading dialog
var img,
dlg = exports.make({
title: lex.get("dialogs.loading"),
content: loaderImg,
parent: parent,
loading: true,
fade: true
});
delay = delay || 0;
img = $(".js-dialog-loading-img", dlg.$dialogJq)[0];
img.onload = function () {
if (!dlg.$closed && !dlg.$hidden) {
// Lazily initialize the content once the image has loaded...
dlg.$delayId = setTimeout(function () {
dlg.show();
}, delay);
}
};
// If a promise is passed in then automatically close the
// dialog box when it's resolved or rejected.
if (promise) {
promise.then(function () {
dlg.close();
}, function (err) {
dlg.close("cancel", err || unknownErr);
});
}
img.src = loaderImg;
return dlg;
}
};
////////////////////////////////////////////////////////////////
// Handle key presses
////////////////////////////////////////////////////////////////
// Handle any key presses
$(function handleKeyPresses() {
$(document).bind("keyup", function (e) {
var esc = e.keyCode === /*esc key*/27,
buttons,
bt,
i,
x;
if (esc && openDialogs.length > 0) {
for (i = openDialogs.length - 1; i >= 0; --i) {
// Find the first dialog that's not hidden or closed.
if (openDialogs[i].$dialogJq &&
!openDialogs[i].isClosed() &&
!openDialogs[i].isHidden()) {
buttons = openDialogs[i].$params.buttons;
if (esc) {
// Invoke any buttons that handle an escape press
for (x = 0; x < buttons.length; ++x) {
bt = buttons[x];
if (bt.esc && canClick(bt.jq)) {
bt.jq.click();
}
}
}
break;
}
}
}
});
});
return exports;
});