/** * @module nyc/Dialog */ import $ from 'jquery' import Container from 'nyc/Container' /** * @desc Class for alert, yes/no and input dialogs * @public * @class * @extends module:nyc/Container~Container */ class Dialog extends Container { /** * @desc Create an instance of Dialog * @public * @constructor * @param {module:nyc/Dialog~Dialog.Options} options Constructor options */ constructor(options) { options = options || {} options.target = options.target || 'body' super($(Dialog.HTML)) $(options.target).append(this.getContainer().addClass(options.css)) /** * @private * @member {boolean} */ this.open = false /** * @private * @member {jQuery} */ this.okBtn = this.find('.btn-ok') /** * @private * @member {jQuery} */ this.yesNoBtns = this.find('.btn-yes, .btn-no') /** * @private * @member {jQuery} */ this.inputBtns = this.find('.btn-submit, .btn-cancel') /** * @private * @member {jQuery} */ this.field = this.find('input') /** * @private * @member {jQuery} */ this.msg = this.find('.dia-msg') } /** * @desc Show the ok dialog * @public * @method * @param {module:nyc/Dialog~Dialog.ShowOptions} options Dialog options * @return {Promise<boolean>} The async result of the user action */ ok(options) { this.buttons(Dialog.Type.OK, options) this.show(Dialog.Type.OK, options) this.okBtn.focus() const dia = this const ok = this.okBtn return new Promise(resolve => { ok.one('click', event => { dia.checkHref(event) dia.hide() resolve(true) }) }) } /** * @desc Show the input dialog * @public * @method * @param {module:nyc/Dialog~Dialog.ShowOptions} options Dialog options * @return {Promise<string|boolean|undefined>} The async result of the user action */ input(options) { const field = this.field this.buttons(Dialog.Type.INPUT, options) field.attr('placeholder', options.placeholder || '') this.show(Dialog.Type.INPUT, options) field.focus() const dia = this const input = this.inputBtns return new Promise(resolve => { const keyup = (event) => { dia.hndlKey(resolve, dia, event) } $(document).keyup(keyup) input.one('click', (event) => { const cancel = $(event.target).hasClass('btn-cancel') $(document).off('keyup', keyup) dia.hide() resolve(cancel ? undefined : field.val()) }) }) } /** * @desc Show the yes-no dialog * @public * @method * @param {module:nyc/Dialog~Dialog.ShowOptions} options Dialog options * @return {Promise<boolean>} The async result of the user action */ yesNo(options) { this.buttons(Dialog.Type.YES_NO, options) this.show(Dialog.Type.YES_NO, options) this.find('.btn-yes').focus() const dia = this const yesNo = this.yesNoBtns return new Promise(resolve => { yesNo.one('click', event => { dia.checkHref(event) dia.hide() resolve($(event.target).hasClass('btn-yes')) }) }) } /** * @desc Show the yes-no-cancel dialog * @public * @method * @param {module:nyc/Dialog~Dialog.ShowOptions} options Dialog options * @return {Promise<boolean|undefined>} The async result of the user action */ yesNoCancel(options) { this.buttons(Dialog.Type.YES_NO_CANCEL, options) this.show(Dialog.Type.YES_NO_CANCEL, options) this.find('.btn-yes').focus() const dia = this const yesNo = this.yesNoBtns const cancel = this.find('.btn-cancel') return new Promise(resolve => { const keyup = (event) => { dia.hndlKey(resolve, dia, event) } $(document).keyup(keyup) yesNo.one('click', event => { dia.checkHref(event) $(document).off('keyup', keyup) dia.hide() resolve($(event.target).hasClass('btn-yes')) }) cancel.one('click', event => { dia.checkHref(event) $(document).off('keyup', keyup) dia.hide() resolve(undefined) }) }) } /** * @private * @method * @param {jQuery.Event} event Event object */ checkHref(event) { if ($(event.target).attr('href') === '#') { event.preventDefault() } } /** * @private * @method * @param {module:nyc/Dialog~Dialog.Type} type Type of button * @param {Object} options Button options (texts and hyperlinks) */ buttons(type, options) { const buttonText = options.buttonText || [] const buttonHref = options.buttonHref || [] switch (type) { case Dialog.Type.OK: this.find('.btn-ok').html(buttonText[0] || 'OK') .attr('href', buttonHref[0] || '#') break case Dialog.Type.INPUT: this.find('.btn-submit').html(buttonText[0] || 'Submit') .attr('href', buttonHref[0] || '#') this.find('.btn-cancel').html(buttonText[1] || 'Cancel') .attr('href', buttonHref[1] || '#') break case Dialog.Type.YES_NO: this.find('.btn-yes').html(buttonText[0] || 'Yes') .attr('href', buttonHref[0] || '#') this.find('.btn-no').html(buttonText[1] || 'No') .attr('href', buttonHref[1] || '#') break case Dialog.Type.YES_NO_CANCEL: this.find('.btn-yes').html(buttonText[0] || 'Yes') .attr('href', buttonHref[0] || '#') this.find('.btn-no').html(buttonText[1] || 'No') .attr('href', buttonHref[1] || '#') this.find('.btn-cancel').html(buttonText[2] || 'Cancel') .attr('href', buttonHref[2] || '#') break default: break } } /** * @private * @method * @param {module:nyc/Dialog~Dialog.Type} type Type of dialog * @param {module:nyc/Dialog~Dialog.ShowOptions} options Dialog options */ show(type, options) { this.open = true $('*').on('focus', $.proxy(this.trapFocus, this)) this.currentType = type this.getContainer().removeClass('dia-3-btns') this.find('.ui-link').removeClass('ui-link') this.inputBtns.css('display', type === Dialog.Type.INPUT ? 'inline-block' : 'none') this.field.css('display', type === Dialog.Type.INPUT ? 'block' : 'none') this.okBtn.css('display', type === Dialog.Type.OK ? 'inline-block' : 'none') this.yesNoBtns.css('display', type === Dialog.Type.YES_NO ? 'inline-block' : 'none') if (type === Dialog.Type.YES_NO_CANCEL) { this.getContainer().addClass('dia-3-btns') this.yesNoBtns.css('display', 'inline-block') this.find('.btn-cancel').css('display', 'inline-block') } let message try { message = $(options.message) } catch (ignore) { /* empty */ } if (message && message.length) { this.msg.html(message) } else { this.msg.html(options.message) } this.getContainer().fadeIn() } /** * @private * @method * @param {jQuery.Event} event Event object */ trapFocus(event) { const container = this.getContainer() if (this.open && !$.contains(container.get(0), event.target)) { this.find('.btn:visible').first().focus() } } /** * @private * @method */ hide() { const field = this.field this.open = false $('*').off('focus', $.proxy(this.trapFocus, this)) this.getContainer().fadeOut(() => { field.val('') }) } /** * @private * @method * @param {function()} resolve Resolve function * @param {module:nyc/Dialog~Dialog} dia Dialog instance * @param {jQuery.Event} event Event object */ hndlKey(resolve, dia, event) { if (event.keyCode === 13 && $(event.target).get(0) === dia.field.get(0)) { dia.hide() resolve(dia.field.val()) } else if (event.keyCode === 27) { dia.hide() resolve(undefined) } } } /** * @desc Dialog type * @public * @enum {string} */ Dialog.Type = { /** * @desc Dialog with OK button */ OK: 'ok', /** * @desc Dialog with Yes and No buttons */ YES_NO: 'yes-no', /** * @desc Dialog with Yes, No and Cancel buttons */ YES_NO_CANCEL: 'yes-no-cancel', /** * @desc Dialog to accept user input */ INPUT: 'input' } /** * @desc Dialog options. * @public * @typedef {Object} * @property {jQuery|Element|string} [target=body] The DOM element in which the dialog is displayed * @property {string=} css A CSS class for the dialog */ Dialog.Options /** * @desc Dialog options. * @public * @typedef {Object} * @property {jQuery|Element|string} message Message content * @property {Array<string>=} buttonText Button text list * @property {Array<string>=} buttonHref Button href list * @property {string=} placeholder Placeholder text for input dialog */ Dialog.ShowOptions /** * @private * @const * @type {string} */ Dialog.HTML = '<div class="dia-container" role="dialog">' + '<div class="dia">' + '<div class="dia-msg"></div>' + '<input class="rad-all">' + '<div class="dia-btns">' + '<a class="btn rad-all btn-ok">OK</a>' + '<a class="btn rad-all btn-yes">Yes</a>' + '<a class="btn rad-all btn-no">No</a>' + '<a class="btn rad-all btn-submit">OK</a>' + '<a class="btn rad-all btn-cancel">Cancel</a>' + '</div>' + '</div>' + '</div>' export default Dialog