mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 00:46:50 +02:00
MDL-55727 javascript: Add modal module
This commit is contained in:
parent
e845b96b83
commit
2bcef5594a
22 changed files with 1475 additions and 35 deletions
1
lib/amd/build/custom_interaction_events.min.js
vendored
Normal file
1
lib/amd/build/custom_interaction_events.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
define(["jquery","core/key_codes"],function(a,b){var c={activate:"cie:activate",keyboardActivate:"cie:keyboardactivate",escape:"cie:escape",down:"cie:down",up:"cie:up",home:"cie:home",end:"cie:end",next:"cie:next",previous:"cie:previous",asterix:"cie:asterix",scrollTop:"cie:scrollTop",scrollBottom:"cie:scrollBottom",ctrlPageUp:"cie:ctrlPageUp",ctrlPageDown:"cie:ctrlPageDown",enter:"cie:enter"},d=function(a,b){return b=b||[],!(!b.length||b.indexOf(a)===-1)},e=function(a){return a.shiftKey||a.metaKey||a.altKey||a.ctrlKey},f=function(b,c,d){b.off("keydown."+c).on("keydown."+c,function(b){e(b)||b.keyCode==d&&a(b.target).trigger(c,[{originalEvent:b}])})},g=function(d){d.off("click.cie.activate").on("click.cie.activate",function(b){a(b.target).trigger(c.activate,[{originalEvent:b}])}),d.off("keydown.cie.activate").on("keydown.cie.activate",function(d){e(d)||d.keyCode!=b.enter&&d.keyCode!=b.space||a(d.target).trigger(c.activate,[{originalEvent:d}])})},h=function(d){d.off("keydown.cie.keyboardactivate").on("keydown.cie.keyboardactivate",function(d){e(d)||d.keyCode!=b.enter&&d.keyCode!=b.space||a(d.target).trigger(c.keyboardActivate,[{originalEvent:d}])})},i=function(a){f(a,c.escape,b.escape)},j=function(a){f(a,c.down,b.arrowDown)},k=function(a){f(a,c.up,b.arrowUp)},l=function(a){f(a,c.home,b.home)},m=function(a){f(a,c.end,b.end)},n=function(d){var e="rtl"==a("html").attr("dir")?b.arrowLeft:b.arrowRight;f(d,c.next,e)},o=function(d){var e="rtl"==a("html").attr("dir")?b.arrowRight:b.arrowLeft;f(d,c.previous,e)},p=function(a){f(a,c.asterix,b.asterix)},q=function(a){a.off("scroll.cie.scrollTop").on("scroll.cie.scrollTop",function(){var b=a.scrollTop();0===b&&a.trigger(c.scrollTop)})},r=function(a){a.off("scroll.cie.scrollBottom").on("scroll.cie.scrollBottom",function(){var b=a.scrollTop(),d=a.innerHeight(),e=a[0].scrollHeight;b+d>=e&&a.trigger(c.scrollBottom)})},s=function(d){d.off("keydown.cie.ctrlpageup").on("keydown.cie.ctrlpageup",function(d){d.ctrlKey&&d.keyCode==b.pageUp&&a(d.target).trigger(c.ctrlPageUp,[{originalEvent:d}])})},t=function(d){d.off("keydown.cie.ctrlpagedown").on("keydown.cie.ctrlpagedown",function(d){d.ctrlKey&&d.keyCode==b.pageDown&&a(d.target).trigger(c.ctrlPageDown,[{originalEvent:d}])})},u=function(a){f(a,c.enter,b.enter)},v=function(){var a={};return a[c.activate]=g,a[c.keyboardActivate]=h,a[c.escape]=i,a[c.down]=j,a[c.up]=k,a[c.home]=l,a[c.end]=m,a[c.next]=n,a[c.previous]=o,a[c.asterix]=p,a[c.scrollTop]=q,a[c.scrollBottom]=r,a[c.ctrlPageUp]=s,a[c.ctrlPageDown]=t,a[c.enter]=u,a},w=function(b,c){b=a(b),c=c||[],b.length&&c.length&&a.each(v(),function(a,e){d(a,c)&&e(b)})};return{define:w,events:c}});
|
1
lib/amd/build/key_codes.min.js
vendored
Normal file
1
lib/amd/build/key_codes.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
define(function(){return{tab:9,enter:13,escape:27,space:32,end:35,home:36,arrowLeft:37,arrowUp:38,arrowRight:39,arrowDown:40,8:56,asterix:106,pageUp:33,pageDown:34}});
|
1
lib/amd/build/modal.min.js
vendored
Normal file
1
lib/amd/build/modal.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
lib/amd/build/modal_backdrop.min.js
vendored
Normal file
1
lib/amd/build/modal_backdrop.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
define(["jquery","core/templates","core/notification"],function(a,b,c){var d={ROOT:'[data-region="modal-backdrop"]'},e=function(b){this.root=a(b),this.isAttached=!1,this.root.is(d.ROOT)||c.exception({message:"Element is not a modal backdrop"})};return e.prototype.getRoot=function(){return this.root},e.prototype.attachToDOM=function(){this.isAttached||(a("body").append(this.root),this.isAttached=!0)},e.prototype.setZIndex=function(a){this.root.css("z-index",a)},e.prototype.isVisible=function(){return this.root.hasClass("show")},e.prototype.hasTransitions=function(){return this.getRoot().hasClass("fade")},e.prototype.show=function(){this.isVisible()||(this.isAttached||this.attachToDOM(),this.root.removeClass("hide").addClass("show"))},e.prototype.hide=function(){this.isVisible()&&(this.hasTransitions()?this.getRoot().one("transitionend webkitTransitionEnd oTransitionEnd",function(){this.getRoot().removeClass("show").addClass("hide")}.bind(this)):this.getRoot().removeClass("show").addClass("hide"))},e.prototype.destroy=function(){this.root.remove()},e});
|
1
lib/amd/build/modal_events.min.js
vendored
Normal file
1
lib/amd/build/modal_events.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
define([],function(){return{shown:"modal:shown",hidden:"modal:hidden",destroyed:"modal:destroyed",save:"modal-save-cancel:save",cancel:"modal-save-cancel:cancel"}});
|
1
lib/amd/build/modal_factory.min.js
vendored
Normal file
1
lib/amd/build/modal_factory.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
define(["jquery","core/modal_events","core/modal","core/modal_save_cancel","core/templates","core/notification","core/custom_interaction_events"],function(a,b,c,d,e,f,g){var h={DEFAULT:"core/modal",SAVE_CANCEL:"core/modal_save_cancel"},i={DEFAULT:c,SAVE_CANCEL:d},j={DEFAULT:"DEFAULT",SAVE_CANCEL:"SAVE_CANCEL"},k=function(a,c){"undefined"!=typeof c&&(g.define(c,[g.events.activate]),c.on(g.events.activate,function(){a.show()}),a.getRoot().on(b.hidden,function(){c.focus()}))},l=function(b,c,d){c=a(c);var e=i[b],f=new e(c);return k(f,d),f},m=function(b,c){var d=h[b];return e.render(d,{}).then(function(d){var e=a(d);return l(b,e,c)}).fail(f.exception)},n=function(a,b){var c=a.type||j.DEFAULT,d=!!a.large;return j[c]||(c=j.DEFAULT),m(c,b).then(function(b){return"undefined"!=typeof a.title&&b.setTitle(a.title),"undefined"!=typeof a.body&&b.setBody(a.body),"undefined"!=typeof a.footer&&b.setFooter(a.footer),d&&b.setLarge(),b})};return{create:n,types:j}});
|
1
lib/amd/build/modal_save_cancel.min.js
vendored
Normal file
1
lib/amd/build/modal_save_cancel.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_events"],function(a,b,c,d,e){var f={SAVE_BUTTON:'[data-action="save"]',CANCEL_BUTTON:'[data-action="cancel"]'},g=function(a){d.call(this,a),this.getFooter().find(f.SAVE_BUTTON).length||b.exception({message:"No save button found"}),this.getFooter().find(f.CANCEL_BUTTON).length||b.exception({message:"No cancel button found"})};return g.prototype=Object.create(d.prototype),g.prototype.constructor=g,g.prototype.setFooter=function(){b.exception({message:"Can not change the footer of a save cancel modal"})},g.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,f.SAVE_BUTTON,function(b,c){var d=a.Event(e.save);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,f.CANCEL_BUTTON,function(b,c){var d=a.Event(e.cancel);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g});
|
|
@ -52,7 +52,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
* @private
|
||||
* @param {string} eventType name of the event (see events above)
|
||||
* @param {array} include the list of events to be added
|
||||
* @return bool true if the event should be added, false otherwise.
|
||||
* @return {bool} true if the event should be added, false otherwise.
|
||||
*/
|
||||
var shouldAddEvent = function(eventType, include) {
|
||||
include = include || [];
|
||||
|
@ -70,7 +70,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
* @method isModifierPressed
|
||||
* @private
|
||||
* @param {event} e jQuery event
|
||||
* @return bool true if shift, meta (command on Mac), alt or ctrl are pressed
|
||||
* @return {bool} true if shift, meta (command on Mac), alt or ctrl are pressed
|
||||
*/
|
||||
var isModifierPressed = function(e) {
|
||||
return (e.shiftKey || e.metaKey || e.altKey || e.ctrlKey);
|
||||
|
@ -81,6 +81,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addKeyboardEvent
|
||||
* @private
|
||||
* @param {object} element A jQuery object of the element to bind events to
|
||||
* @param {string} event The custom interaction event name
|
||||
* @param {int} keyCode The key code.
|
||||
*/
|
||||
|
@ -100,7 +101,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addActivateListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addActivateListener = function(element) {
|
||||
element.off('click.cie.activate').on('click.cie.activate', function(e) {
|
||||
|
@ -121,7 +122,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addKeyboardActivateListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addKeyboardActivateListener = function(element) {
|
||||
element.off('keydown.cie.keyboardactivate').on('keydown.cie.keyboardactivate', function(e) {
|
||||
|
@ -139,7 +140,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addEscapeListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addEscapeListener = function(element) {
|
||||
addKeyboardEvent(element, events.escape, keyCodes.escape);
|
||||
|
@ -151,7 +152,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addDownListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addDownListener = function(element) {
|
||||
addKeyboardEvent(element, events.down, keyCodes.arrowDown);
|
||||
|
@ -163,7 +164,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addUpListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addUpListener = function(element) {
|
||||
addKeyboardEvent(element, events.up, keyCodes.arrowUp);
|
||||
|
@ -175,7 +176,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addHomeListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addHomeListener = function(element) {
|
||||
addKeyboardEvent(element, events.home, keyCodes.home);
|
||||
|
@ -187,7 +188,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addEndListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addEndListener = function(element) {
|
||||
addKeyboardEvent(element, events.end, keyCodes.end);
|
||||
|
@ -199,7 +200,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addNextListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addNextListener = function(element) {
|
||||
// Left and right are flipped in RTL mode.
|
||||
|
@ -214,7 +215,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addPreviousListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addPreviousListener = function(element) {
|
||||
// Left and right are flipped in RTL mode.
|
||||
|
@ -229,7 +230,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addAsterixListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addAsterixListener = function(element) {
|
||||
addKeyboardEvent(element, events.asterix, keyCodes.asterix);
|
||||
|
@ -242,7 +243,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addScrollTopListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addScrollTopListener = function(element) {
|
||||
element.off('scroll.cie.scrollTop').on('scroll.cie.scrollTop', function() {
|
||||
|
@ -259,7 +260,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addScrollBottomListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addScrollBottomListener = function(element) {
|
||||
element.off('scroll.cie.scrollBottom').on('scroll.cie.scrollBottom', function() {
|
||||
|
@ -279,7 +280,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addCtrlPageUpListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addCtrlPageUpListener = function(element) {
|
||||
element.off('keydown.cie.ctrlpageup').on('keydown.cie.ctrlpageup', function(e) {
|
||||
|
@ -297,7 +298,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addCtrlPageDownListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addCtrlPageDownListener = function(element) {
|
||||
element.off('keydown.cie.ctrlpagedown').on('keydown.cie.ctrlpagedown', function(e) {
|
||||
|
@ -315,7 +316,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method addEnterListener
|
||||
* @private
|
||||
* @param {jQuery object} element jQuery object to add event listeners to
|
||||
* @param {object} element jQuery object to add event listeners to
|
||||
*/
|
||||
var addEnterListener = function(element) {
|
||||
addKeyboardEvent(element, events.enter, keyCodes.enter);
|
||||
|
@ -326,7 +327,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method getHandlers
|
||||
* @private
|
||||
* @return {jQuery object} object key of event names and value of handler functions
|
||||
* @return {object} object key of event names and value of handler functions
|
||||
*/
|
||||
var getHandlers = function() {
|
||||
var handlers = {};
|
||||
|
@ -355,7 +356,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
|||
*
|
||||
* @method define
|
||||
* @public
|
||||
* @param {jQuery object} element the DOM element to register event listeners on
|
||||
* @param {object} element the DOM element to register event listeners on
|
||||
* @param {array} include the array of events to be triggered
|
||||
*/
|
||||
var define = function(element, include) {
|
||||
|
|
|
@ -26,19 +26,19 @@
|
|||
define(function() {
|
||||
|
||||
return /** @alias module:core/key_codes */ {
|
||||
tab: 9,
|
||||
enter: 13,
|
||||
escape: 27,
|
||||
space: 32,
|
||||
end: 35,
|
||||
home: 36,
|
||||
arrowLeft: 37,
|
||||
arrowUp: 38,
|
||||
arrowRight: 39,
|
||||
arrowDown: 40,
|
||||
8: 56,
|
||||
asterix: 106,
|
||||
pageUp: 33,
|
||||
pageDown: 34,
|
||||
'tab': 9,
|
||||
'enter': 13,
|
||||
'escape': 27,
|
||||
'space': 32,
|
||||
'end': 35,
|
||||
'home': 36,
|
||||
'arrowLeft': 37,
|
||||
'arrowUp': 38,
|
||||
'arrowRight': 39,
|
||||
'arrowDown': 40,
|
||||
'8': 56,
|
||||
'asterix': 106,
|
||||
'pageUp': 33,
|
||||
'pageDown': 34,
|
||||
};
|
||||
});
|
||||
|
|
591
lib/amd/src/modal.js
Normal file
591
lib/amd/src/modal.js
Normal file
|
@ -0,0 +1,591 @@
|
|||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Contain the logic for modals.
|
||||
*
|
||||
* @module core/modal
|
||||
* @class modal
|
||||
* @package core
|
||||
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
|
||||
'core/custom_interaction_events', 'core/modal_backdrop', 'core/modal_events'],
|
||||
function($, Templates, Notification, KeyCodes, CustomEvents, ModalBackdrop, ModalEvents) {
|
||||
|
||||
var SELECTORS = {
|
||||
CONTAINER: '[data-region="modal-container"]',
|
||||
MODAL: '[data-region="modal"]',
|
||||
HEADER: '[data-region="header"]',
|
||||
TITLE: '[data-region="title"]',
|
||||
BODY: '[data-region="body"]',
|
||||
FOOTER: '[data-region="footer"]',
|
||||
HIDE: '[data-action="hide"]',
|
||||
DIALOG: '[role=dialog]',
|
||||
MENU_BAR: '[role=menubar]',
|
||||
HAS_Z_INDEX: '.moodle-has-zindex',
|
||||
CAN_RECEIVE_FOCUS: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
|
||||
};
|
||||
|
||||
var TEMPLATES = {
|
||||
LOADING: 'core/loading',
|
||||
BACKDROP: 'core/modal_backdrop',
|
||||
};
|
||||
|
||||
/**
|
||||
* Module singleton for the backdrop to be reused by all Modal instances.
|
||||
*/
|
||||
var backdropPromise;
|
||||
|
||||
/**
|
||||
* Constructor for the Modal.
|
||||
*
|
||||
* @param {object} root The root jQuery element for the modal
|
||||
*/
|
||||
var Modal = function(root) {
|
||||
this.root = $(root);
|
||||
this.modal = this.root.find(SELECTORS.MODAL);
|
||||
this.header = this.modal.find(SELECTORS.HEADER);
|
||||
this.title = this.header.find(SELECTORS.TITLE);
|
||||
this.body = this.modal.find(SELECTORS.BODY);
|
||||
this.footer = this.modal.find(SELECTORS.FOOTER);
|
||||
this.hiddenSiblings = [];
|
||||
this.isAttached = false;
|
||||
this.bodyJS = null;
|
||||
this.footerJS = null;
|
||||
|
||||
if (!this.root.is(SELECTORS.CONTAINER)) {
|
||||
Notification.exception({message: 'Element is not a modal container'});
|
||||
}
|
||||
|
||||
if (!this.modal.length) {
|
||||
Notification.exception({message: 'Container does not contain a modal'});
|
||||
}
|
||||
|
||||
if (!this.header.length) {
|
||||
Notification.exception({message: 'Modal is missing a header region'});
|
||||
}
|
||||
|
||||
if (!this.title.length) {
|
||||
Notification.exception({message: 'Modal header is missing a title region'});
|
||||
}
|
||||
|
||||
if (!this.body.length) {
|
||||
Notification.exception({message: 'Modal is missing a body region'});
|
||||
}
|
||||
|
||||
if (!this.footer.length) {
|
||||
Notification.exception({message: 'Modal is missing a footer region'});
|
||||
}
|
||||
|
||||
this.registerEventListeners();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the modal to the page, if it hasn't already been added. This includes running any
|
||||
* javascript that has been cached until now.
|
||||
*
|
||||
* @method attachToDOM
|
||||
*/
|
||||
Modal.prototype.attachToDOM = function() {
|
||||
if (this.isAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('body').append(this.root);
|
||||
|
||||
// If we'd cached any JS then we can run it how that the modal is
|
||||
// attached to the DOM.
|
||||
if (this.bodyJS) {
|
||||
Templates.runTemplateJS(this.bodyJS);
|
||||
this.bodyJS = null;
|
||||
}
|
||||
|
||||
if (this.footerJS) {
|
||||
Templates.runTemplateJS(this.footerJS);
|
||||
this.footerJS = null;
|
||||
}
|
||||
|
||||
this.isAttached = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Count the number of other visible modals (not including this one).
|
||||
*
|
||||
* @method countOtherVisibleModals
|
||||
* @return {int}
|
||||
*/
|
||||
Modal.prototype.countOtherVisibleModals = function() {
|
||||
var count = 0;
|
||||
$('body').find(SELECTORS.CONTAINER).each(function(index, element) {
|
||||
element = $(element);
|
||||
|
||||
// If we haven't found ourself and the element is visible.
|
||||
if (!this.root.is(element) && element.hasClass('show')) {
|
||||
count++;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modal backdrop.
|
||||
*
|
||||
* @method getBackdrop
|
||||
* @return {object} jQuery promise
|
||||
*/
|
||||
Modal.prototype.getBackdrop = function() {
|
||||
if (!backdropPromise) {
|
||||
backdropPromise = Templates.render(TEMPLATES.BACKDROP, {})
|
||||
.then(function(html) {
|
||||
var element = $(html);
|
||||
|
||||
return new ModalBackdrop(element);
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
}
|
||||
|
||||
return backdropPromise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the root element of this modal.
|
||||
*
|
||||
* @method getRoot
|
||||
* @return {object} jQuery object
|
||||
*/
|
||||
Modal.prototype.getRoot = function() {
|
||||
return this.root;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modal element of this modal.
|
||||
*
|
||||
* @method getModal
|
||||
* @return {object} jQuery object
|
||||
*/
|
||||
Modal.prototype.getModal = function() {
|
||||
return this.modal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modal title element.
|
||||
*
|
||||
* @method getTitle
|
||||
* @return {object} jQuery object
|
||||
*/
|
||||
Modal.prototype.getTitle = function() {
|
||||
return this.title;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modal body element.
|
||||
*
|
||||
* @method getBody
|
||||
* @return {object} jQuery object
|
||||
*/
|
||||
Modal.prototype.getBody = function() {
|
||||
return this.body;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modal footer element.
|
||||
*
|
||||
* @method getFooter
|
||||
* @return {object} jQuery object
|
||||
*/
|
||||
Modal.prototype.getFooter = function() {
|
||||
return this.footer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the modal title element.
|
||||
*
|
||||
* @method setTitle
|
||||
* @param {string} value The title string
|
||||
*/
|
||||
Modal.prototype.setTitle = function(value) {
|
||||
var title = this.getTitle();
|
||||
title.html(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the modal body element.
|
||||
*
|
||||
* This method is overloaded to take either a string
|
||||
* value for the body or a jQuery promise that is resolved with HTML and Javascript
|
||||
* most commonly from a Templates.render call.
|
||||
*
|
||||
* @method setBody
|
||||
* @param {(string|object)} value The body string or jQuery promise
|
||||
*/
|
||||
Modal.prototype.setBody = function(value) {
|
||||
var body = this.getBody();
|
||||
|
||||
if (typeof value === 'string') {
|
||||
// Just set the value if it's a string.
|
||||
body.html(value);
|
||||
} else {
|
||||
// Otherwise we assume it's a promise to be resolved with
|
||||
// html and javascript.
|
||||
Templates.render(TEMPLATES.LOADING, {}).done(function(html) {
|
||||
body.html(html);
|
||||
|
||||
value.done(function(html, js) {
|
||||
body.html(html);
|
||||
|
||||
if (this.isAttached) {
|
||||
// If we're in the DOM then run the JS immediately.
|
||||
Templates.runTemplateJS(js);
|
||||
} else {
|
||||
// Otherwise cache it to be run when we're attached.
|
||||
this.bodyJS = js;
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the modal footer element.
|
||||
*
|
||||
* This method is overloaded to take either a string
|
||||
* value for the body or a jQuery promise that is resolved with HTML and Javascript
|
||||
* most commonly from a Templates.render call.
|
||||
*
|
||||
* @method setFooter
|
||||
* @param {(string|object)} value The footer string or jQuery promise
|
||||
*/
|
||||
Modal.prototype.setFooter = function(value) {
|
||||
var footer = this.getFooter();
|
||||
|
||||
if (typeof value === 'string') {
|
||||
// Just set the value if it's a string.
|
||||
footer.html(value);
|
||||
} else {
|
||||
// Otherwise we assume it's a promise to be resolved with
|
||||
// html and javascript.
|
||||
Templates.render(TEMPLATES.LOADING, {}).done(function(html) {
|
||||
footer.html(html);
|
||||
|
||||
value.done(function(html, js) {
|
||||
footer.html(html);
|
||||
|
||||
if (this.isAttached) {
|
||||
// If we're in the DOM then run the JS immediately.
|
||||
Templates.runTemplateJS(js);
|
||||
} else {
|
||||
// Otherwise cache it to be run when we're attached.
|
||||
this.footerJS = js;
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark the modal as a large modal.
|
||||
*
|
||||
* @method setLarge
|
||||
*/
|
||||
Modal.prototype.setLarge = function() {
|
||||
if (this.isLarge()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getRoot().addClass('large');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the modal is a large modal.
|
||||
*
|
||||
* @method isLarge
|
||||
* @return {bool}
|
||||
*/
|
||||
Modal.prototype.isLarge = function() {
|
||||
return this.getRoot().hasClass('large');
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark the modal as a small modal.
|
||||
*
|
||||
* @method setSmall
|
||||
*/
|
||||
Modal.prototype.setSmall = function() {
|
||||
if (this.isSmall()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getRoot().removeClass('large');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the modal is a small modal.
|
||||
*
|
||||
* @method isSmall
|
||||
* @return {bool}
|
||||
*/
|
||||
Modal.prototype.isSmall = function() {
|
||||
return !this.getRoot().hasClass('large');
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the highest z-index value currently on the page.
|
||||
*
|
||||
* @method calculateZIndex
|
||||
* @return {int}
|
||||
*/
|
||||
Modal.prototype.calculateZIndex = function() {
|
||||
var items = $(SELECTORS.DIALOG + ', ' + SELECTORS.MENU_BAR + ', ' + SELECTORS.HAS_Z_INDEX);
|
||||
var zIndex = parseInt(this.root.css('z-index'));
|
||||
|
||||
items.each(function(index, item) {
|
||||
item = $(item);
|
||||
// Note that webkit browsers won't return the z-index value from the CSS stylesheet
|
||||
// if the element doesn't have a position specified. Instead it'll return "auto".
|
||||
var itemZIndex = item.css('z-index') ? parseInt(item.css('z-index')) : 0;
|
||||
|
||||
if (itemZIndex > zIndex) {
|
||||
zIndex = itemZIndex;
|
||||
}
|
||||
});
|
||||
|
||||
return zIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this modal is visible.
|
||||
*
|
||||
* @method isVisible
|
||||
* @return {bool}
|
||||
*/
|
||||
Modal.prototype.isVisible = function() {
|
||||
return this.root.hasClass('show');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this modal has focus.
|
||||
*
|
||||
* @method hasFocus
|
||||
* @return {bool}
|
||||
*/
|
||||
Modal.prototype.hasFocus = function() {
|
||||
var target = $(document.activeElement);
|
||||
return this.root.is(target) || this.root.has(target).length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this modal has CSS transitions applied.
|
||||
*
|
||||
* @method hasTransitions
|
||||
* @return {bool}
|
||||
*/
|
||||
Modal.prototype.hasTransitions = function() {
|
||||
return this.getRoot().hasClass('fade');
|
||||
};
|
||||
|
||||
/**
|
||||
* Display this modal. The modal will be attached to the DOM if it hasn't
|
||||
* already been.
|
||||
*
|
||||
* @method show
|
||||
*/
|
||||
Modal.prototype.show = function() {
|
||||
if (this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isAttached) {
|
||||
this.attachToDOM();
|
||||
}
|
||||
|
||||
this.getBackdrop().done(function(backdrop) {
|
||||
var currentIndex = this.calculateZIndex();
|
||||
var newIndex = currentIndex + 2;
|
||||
var newBackdropIndex = newIndex - 1;
|
||||
this.root.css('z-index', newIndex);
|
||||
backdrop.setZIndex(newBackdropIndex);
|
||||
backdrop.show();
|
||||
|
||||
this.root.removeClass('hide').addClass('show');
|
||||
this.accessibilityShow();
|
||||
this.getTitle().focus();
|
||||
$('body').addClass('modal-open');
|
||||
this.root.trigger(ModalEvents.shown, this);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide this modal.
|
||||
*
|
||||
* @method hide
|
||||
*/
|
||||
Modal.prototype.hide = function() {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getBackdrop().done(function(backdrop) {
|
||||
if (!this.countOtherVisibleModals()) {
|
||||
// Hide the backdrop if we're the last open modal.
|
||||
backdrop.hide();
|
||||
$('body').removeClass('modal-open');
|
||||
}
|
||||
|
||||
var currentIndex = parseInt(this.root.css('z-index'));
|
||||
this.root.css('z-index', '');
|
||||
backdrop.setZIndex(currentIndex - 3);
|
||||
|
||||
this.accessibilityHide();
|
||||
|
||||
if (this.hasTransitions()) {
|
||||
// Wait for CSS transitions to complete before hiding the element.
|
||||
this.getRoot().one('transitionend webkitTransitionEnd oTransitionEnd', function() {
|
||||
this.getRoot().removeClass('show').addClass('hide');
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.getRoot().removeClass('show').addClass('hide');
|
||||
}
|
||||
|
||||
this.root.trigger(ModalEvents.hidden, this);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove this modal from the DOM.
|
||||
*
|
||||
* @method destroy
|
||||
*/
|
||||
Modal.prototype.destroy = function() {
|
||||
this.root.remove();
|
||||
this.root.trigger(ModalEvents.destroyed, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the appropriate aria attributes on this dialogue and the other
|
||||
* elements in the DOM to ensure that screen readers are able to navigate
|
||||
* the dialogue popup correctly.
|
||||
*
|
||||
* @method accessibilityShow
|
||||
*/
|
||||
Modal.prototype.accessibilityShow = function() {
|
||||
// We need to get a list containing each sibling element and the shallowest
|
||||
// non-ancestral nodes in the DOM. We can shortcut this a little by leveraging
|
||||
// the fact that this dialogue is always appended to the document body therefore
|
||||
// it's siblings are the shallowest non-ancestral nodes. If that changes then
|
||||
// this code should also be updated.
|
||||
$('body').children().each(function(index, child) {
|
||||
// Skip the current modal.
|
||||
if (!this.root.is(child)) {
|
||||
child = $(child);
|
||||
var hidden = child.attr('aria-hidden');
|
||||
// If they are already hidden we can ignore them.
|
||||
if (hidden !== 'true') {
|
||||
// Save their current state.
|
||||
child.data('previous-aria-hidden', hidden);
|
||||
this.hiddenSiblings.push(child);
|
||||
|
||||
// Hide this node from screen readers.
|
||||
child.attr('aria-hidden', 'true');
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// Make us visible to screen readers.
|
||||
this.root.attr('aria-hidden', 'false');
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the aria visibility on the DOM elements changed when displaying
|
||||
* the dialogue popup and makes the dialogue aria hidden to allow screen
|
||||
* readers to navigate the main page correctly when the dialogue is closed.
|
||||
*
|
||||
* @method accessibilityHide
|
||||
*/
|
||||
Modal.prototype.accessibilityHide = function() {
|
||||
this.root.attr('aria-hidden', 'true');
|
||||
|
||||
// Restore the sibling nodes back to their original values.
|
||||
$.each(this.hiddenSiblings, function(index, sibling) {
|
||||
sibling = $(sibling);
|
||||
var previousValue = sibling.data('previous-aria-hidden');
|
||||
// If the element didn't previously have an aria-hidden attribute
|
||||
// then we can just remove the one we set.
|
||||
if (typeof previousValue == 'undefined') {
|
||||
sibling.removeAttr('aria-hidden');
|
||||
} else {
|
||||
// Otherwise set it back to the old value (which will be false).
|
||||
sibling.attr('aria-hidden', previousValue);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear the cache. No longer need to store these.
|
||||
this.hiddenSiblings = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the tab event to lock focus within this modal.
|
||||
*
|
||||
* @method handleTabLock
|
||||
* @param {event} e The tab key jQuery event
|
||||
*/
|
||||
Modal.prototype.handleTabLock = function(e) {
|
||||
if (!this.hasFocus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var target = $(document.activeElement);
|
||||
var focusableElements = this.modal.find(SELECTORS.CAN_RECEIVE_FOCUS);
|
||||
var firstFocusable = focusableElements.first();
|
||||
var lastFocusable = focusableElements.last();
|
||||
|
||||
if (target.is(firstFocusable) && e.shiftKey) {
|
||||
lastFocusable.focus();
|
||||
e.preventDefault();
|
||||
} else if (target.is(lastFocusable) && !e.shiftKey) {
|
||||
firstFocusable.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up all of the event handling for the modal.
|
||||
*
|
||||
* @method registerEventListeners
|
||||
*/
|
||||
Modal.prototype.registerEventListeners = function() {
|
||||
this.getRoot().on('keydown', function(e) {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.keyCode == KeyCodes.tab) {
|
||||
this.handleTabLock(e);
|
||||
} else if (e.keyCode == KeyCodes.escape) {
|
||||
this.hide();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
CustomEvents.define(this.getModal(), [CustomEvents.events.activate]);
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.HIDE, function(e, data) {
|
||||
this.hide();
|
||||
data.originalEvent.preventDefault();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
return Modal;
|
||||
});
|
148
lib/amd/src/modal_backdrop.js
Normal file
148
lib/amd/src/modal_backdrop.js
Normal file
|
@ -0,0 +1,148 @@
|
|||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Contain the logic for modal backdrops.
|
||||
*
|
||||
* @module core/modal_backdrop
|
||||
* @class modal_backdrop
|
||||
* @package core
|
||||
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/templates', 'core/notification'],
|
||||
function($, Templates, Notification) {
|
||||
|
||||
var SELECTORS = {
|
||||
ROOT: '[data-region="modal-backdrop"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for ModalBackdrop.
|
||||
*
|
||||
* @param {object} root The root element for the modal backdrop
|
||||
*/
|
||||
var ModalBackdrop = function(root) {
|
||||
this.root = $(root);
|
||||
this.isAttached = false;
|
||||
|
||||
if (!this.root.is(SELECTORS.ROOT)) {
|
||||
Notification.exception({message: 'Element is not a modal backdrop'});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the root element of this modal backdrop.
|
||||
*
|
||||
* @method getRoot
|
||||
* @return {object} jQuery object
|
||||
*/
|
||||
ModalBackdrop.prototype.getRoot = function() {
|
||||
return this.root;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the modal backdrop to the page, if it hasn't already been added.
|
||||
*
|
||||
* @method attachToDOM
|
||||
*/
|
||||
ModalBackdrop.prototype.attachToDOM = function() {
|
||||
if (this.isAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('body').append(this.root);
|
||||
this.isAttached = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the z-index value for this backdrop.
|
||||
*
|
||||
* @method setZIndex
|
||||
* @param {int} value The z-index value
|
||||
*/
|
||||
ModalBackdrop.prototype.setZIndex = function(value) {
|
||||
this.root.css('z-index', value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this backdrop is visible.
|
||||
*
|
||||
* @method isVisible
|
||||
* @return {bool}
|
||||
*/
|
||||
ModalBackdrop.prototype.isVisible = function() {
|
||||
return this.root.hasClass('show');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this backdrop has CSS transitions applied.
|
||||
*
|
||||
* @method hasTransitions
|
||||
* @return {bool}
|
||||
*/
|
||||
ModalBackdrop.prototype.hasTransitions = function() {
|
||||
return this.getRoot().hasClass('fade');
|
||||
};
|
||||
|
||||
/**
|
||||
* Display this backdrop. The backdrop will be attached to the DOM if it hasn't
|
||||
* already been.
|
||||
*
|
||||
* @method show
|
||||
*/
|
||||
ModalBackdrop.prototype.show = function() {
|
||||
if (this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isAttached) {
|
||||
this.attachToDOM();
|
||||
}
|
||||
|
||||
this.root.removeClass('hide').addClass('show');
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide this backdrop.
|
||||
*
|
||||
* @method hide
|
||||
*/
|
||||
ModalBackdrop.prototype.hide = function() {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hasTransitions()) {
|
||||
// Wait for CSS transitions to complete before hiding the element.
|
||||
this.getRoot().one('transitionend webkitTransitionEnd oTransitionEnd', function() {
|
||||
this.getRoot().removeClass('show').addClass('hide');
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.getRoot().removeClass('show').addClass('hide');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove this backdrop from the DOM.
|
||||
*
|
||||
* @method destroy
|
||||
*/
|
||||
ModalBackdrop.prototype.destroy = function() {
|
||||
this.root.remove();
|
||||
};
|
||||
|
||||
return ModalBackdrop;
|
||||
});
|
35
lib/amd/src/modal_events.js
Normal file
35
lib/amd/src/modal_events.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Contain the events a modal can fire.
|
||||
*
|
||||
* @module core/modal_events
|
||||
* @class modal_events
|
||||
* @package core
|
||||
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([], function() {
|
||||
return {
|
||||
// Default events.
|
||||
shown: 'modal:shown',
|
||||
hidden: 'modal:hidden',
|
||||
destroyed: 'modal:destroyed',
|
||||
// ModalSaveCancel events.
|
||||
save: 'modal-save-cancel:save',
|
||||
cancel: 'modal-save-cancel:cancel',
|
||||
};
|
||||
});
|
150
lib/amd/src/modal_factory.js
Normal file
150
lib/amd/src/modal_factory.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Create a modal.
|
||||
*
|
||||
* @module core/modal_factory
|
||||
* @class modal_factory
|
||||
* @package core
|
||||
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/modal_events', 'core/modal', 'core/modal_save_cancel', 'core/templates',
|
||||
'core/notification', 'core/custom_interaction_events'],
|
||||
function($, ModalEvents, Modal, ModalSaveCancel, Templates, Notification, CustomEvents) {
|
||||
|
||||
// The templates for each type of modal.
|
||||
var TEMPLATES = {
|
||||
DEFAULT: 'core/modal',
|
||||
SAVE_CANCEL: 'core/modal_save_cancel',
|
||||
};
|
||||
|
||||
// The JS classes for each type of modal.
|
||||
var CLASSES = {
|
||||
DEFAULT: Modal,
|
||||
SAVE_CANCEL: ModalSaveCancel,
|
||||
};
|
||||
|
||||
// The available types of modals.
|
||||
var TYPES = {
|
||||
DEFAULT: 'DEFAULT',
|
||||
SAVE_CANCEL: 'SAVE_CANCEL',
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up the events required to show the modal and return focus when the modal
|
||||
* is closed.
|
||||
*
|
||||
* @method setUpTrigger
|
||||
* @param {object} modal The modal instance
|
||||
* @param {object} triggerElement The jQuery element to open the modal
|
||||
*/
|
||||
var setUpTrigger = function(modal, triggerElement) {
|
||||
if (typeof triggerElement != 'undefined') {
|
||||
CustomEvents.define(triggerElement, [CustomEvents.events.activate]);
|
||||
triggerElement.on(CustomEvents.events.activate, function() {
|
||||
modal.show();
|
||||
});
|
||||
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
triggerElement.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the correct instance of a modal based on the givem type. Sets up
|
||||
* the trigger between the modal and the trigger element.
|
||||
*
|
||||
* @method createFromElement
|
||||
* @param {string} type A modal type (see TYPES)
|
||||
* @param {object} modalElement The modal HTML jQuery object
|
||||
* @param {object} triggerElement The trigger HTML jQuery object
|
||||
* @return {object} Modal instance
|
||||
*/
|
||||
var createFromElement = function(type, modalElement, triggerElement) {
|
||||
modalElement = $(modalElement);
|
||||
var className = CLASSES[type];
|
||||
var modal = new className(modalElement);
|
||||
setUpTrigger(modal, triggerElement);
|
||||
|
||||
return modal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the correct modal instance for the given type, including loading
|
||||
* the correct template and setting up the trigger relationship with the
|
||||
* trigger element.
|
||||
*
|
||||
* @method createFromType
|
||||
* @param {string} type A modal type (see TYPES)
|
||||
* @param {object} triggerElement The trigger HTML jQuery object
|
||||
* @return {promise} Resolved with a Modal instance
|
||||
*/
|
||||
var createFromType = function(type, triggerElement) {
|
||||
var templateName = TEMPLATES[type];
|
||||
|
||||
return Templates.render(templateName, {})
|
||||
.then(function(html) {
|
||||
var modalElement = $(html);
|
||||
return createFromElement(type, modalElement, triggerElement);
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a Modal instance.
|
||||
*
|
||||
* @method create
|
||||
* @param {object} modalConfig The configuration to create the modal instance
|
||||
* @param {object} triggerElement The trigger HTML jQuery object
|
||||
* @return {promise} Resolved with a Modal instance
|
||||
*/
|
||||
var create = function(modalConfig, triggerElement) {
|
||||
var type = modalConfig.type || TYPES.DEFAULT;
|
||||
var isLarge = modalConfig.large ? true : false;
|
||||
|
||||
if (!TYPES[type]) {
|
||||
type = TYPES.DEFAULT;
|
||||
}
|
||||
|
||||
return createFromType(type, triggerElement)
|
||||
.then(function(modal) {
|
||||
if (typeof modalConfig.title != 'undefined') {
|
||||
modal.setTitle(modalConfig.title);
|
||||
}
|
||||
|
||||
if (typeof modalConfig.body != 'undefined') {
|
||||
modal.setBody(modalConfig.body);
|
||||
}
|
||||
|
||||
if (typeof modalConfig.footer != 'undefined') {
|
||||
modal.setFooter(modalConfig.footer);
|
||||
}
|
||||
|
||||
if (isLarge) {
|
||||
modal.setLarge();
|
||||
}
|
||||
|
||||
return modal;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
create: create,
|
||||
types: TYPES,
|
||||
};
|
||||
});
|
92
lib/amd/src/modal_save_cancel.js
Normal file
92
lib/amd/src/modal_save_cancel.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Contain the logic for the save/cancel modal.
|
||||
*
|
||||
* @module core/modal_save_cancel
|
||||
* @class modal_save_cancel
|
||||
* @package core
|
||||
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_events'],
|
||||
function($, Notification, CustomEvents, Modal, ModalEvents) {
|
||||
|
||||
var SELECTORS = {
|
||||
SAVE_BUTTON: '[data-action="save"]',
|
||||
CANCEL_BUTTON: '[data-action="cancel"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for the Modal.
|
||||
*
|
||||
* @param {object} root The root jQuery element for the modal
|
||||
*/
|
||||
var ModalSaveCancel = function(root) {
|
||||
Modal.call(this, root);
|
||||
|
||||
if (!this.getFooter().find(SELECTORS.SAVE_BUTTON).length) {
|
||||
Notification.exception({message: 'No save button found'});
|
||||
}
|
||||
|
||||
if (!this.getFooter().find(SELECTORS.CANCEL_BUTTON).length) {
|
||||
Notification.exception({message: 'No cancel button found'});
|
||||
}
|
||||
};
|
||||
|
||||
ModalSaveCancel.prototype = Object.create(Modal.prototype);
|
||||
ModalSaveCancel.prototype.constructor = ModalSaveCancel;
|
||||
|
||||
/**
|
||||
* Override parent implementation to prevent changing the footer content.
|
||||
*/
|
||||
ModalSaveCancel.prototype.setFooter = function() {
|
||||
Notification.exception({message: 'Can not change the footer of a save cancel modal'});
|
||||
return;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up all of the event handling for the modal.
|
||||
*
|
||||
* @method registerEventListeners
|
||||
*/
|
||||
ModalSaveCancel.prototype.registerEventListeners = function() {
|
||||
// Apply parent event listeners.
|
||||
Modal.prototype.registerEventListeners.call(this);
|
||||
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, function(e, data) {
|
||||
var saveEvent = $.Event(ModalEvents.save);
|
||||
this.getRoot().trigger(saveEvent, this);
|
||||
|
||||
if (!saveEvent.isDefaultPrevented()) {
|
||||
this.hide();
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, function(e, data) {
|
||||
var cancelEvent = $.Event(ModalEvents.cancel);
|
||||
this.getRoot().trigger(cancelEvent, this);
|
||||
|
||||
if (!cancelEvent.isDefaultPrevented()) {
|
||||
this.hide();
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
return ModalSaveCancel;
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue