mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 08:56:36 +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
|
* @private
|
||||||
* @param {string} eventType name of the event (see events above)
|
* @param {string} eventType name of the event (see events above)
|
||||||
* @param {array} include the list of events to be added
|
* @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) {
|
var shouldAddEvent = function(eventType, include) {
|
||||||
include = include || [];
|
include = include || [];
|
||||||
|
@ -70,7 +70,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
* @method isModifierPressed
|
* @method isModifierPressed
|
||||||
* @private
|
* @private
|
||||||
* @param {event} e jQuery event
|
* @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) {
|
var isModifierPressed = function(e) {
|
||||||
return (e.shiftKey || e.metaKey || e.altKey || e.ctrlKey);
|
return (e.shiftKey || e.metaKey || e.altKey || e.ctrlKey);
|
||||||
|
@ -81,6 +81,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addKeyboardEvent
|
* @method addKeyboardEvent
|
||||||
* @private
|
* @private
|
||||||
|
* @param {object} element A jQuery object of the element to bind events to
|
||||||
* @param {string} event The custom interaction event name
|
* @param {string} event The custom interaction event name
|
||||||
* @param {int} keyCode The key code.
|
* @param {int} keyCode The key code.
|
||||||
*/
|
*/
|
||||||
|
@ -100,7 +101,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addActivateListener
|
* @method addActivateListener
|
||||||
* @private
|
* @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) {
|
var addActivateListener = function(element) {
|
||||||
element.off('click.cie.activate').on('click.cie.activate', function(e) {
|
element.off('click.cie.activate').on('click.cie.activate', function(e) {
|
||||||
|
@ -121,7 +122,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addKeyboardActivateListener
|
* @method addKeyboardActivateListener
|
||||||
* @private
|
* @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) {
|
var addKeyboardActivateListener = function(element) {
|
||||||
element.off('keydown.cie.keyboardactivate').on('keydown.cie.keyboardactivate', function(e) {
|
element.off('keydown.cie.keyboardactivate').on('keydown.cie.keyboardactivate', function(e) {
|
||||||
|
@ -139,7 +140,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addEscapeListener
|
* @method addEscapeListener
|
||||||
* @private
|
* @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) {
|
var addEscapeListener = function(element) {
|
||||||
addKeyboardEvent(element, events.escape, keyCodes.escape);
|
addKeyboardEvent(element, events.escape, keyCodes.escape);
|
||||||
|
@ -151,7 +152,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addDownListener
|
* @method addDownListener
|
||||||
* @private
|
* @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) {
|
var addDownListener = function(element) {
|
||||||
addKeyboardEvent(element, events.down, keyCodes.arrowDown);
|
addKeyboardEvent(element, events.down, keyCodes.arrowDown);
|
||||||
|
@ -163,7 +164,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addUpListener
|
* @method addUpListener
|
||||||
* @private
|
* @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) {
|
var addUpListener = function(element) {
|
||||||
addKeyboardEvent(element, events.up, keyCodes.arrowUp);
|
addKeyboardEvent(element, events.up, keyCodes.arrowUp);
|
||||||
|
@ -175,7 +176,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addHomeListener
|
* @method addHomeListener
|
||||||
* @private
|
* @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) {
|
var addHomeListener = function(element) {
|
||||||
addKeyboardEvent(element, events.home, keyCodes.home);
|
addKeyboardEvent(element, events.home, keyCodes.home);
|
||||||
|
@ -187,7 +188,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addEndListener
|
* @method addEndListener
|
||||||
* @private
|
* @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) {
|
var addEndListener = function(element) {
|
||||||
addKeyboardEvent(element, events.end, keyCodes.end);
|
addKeyboardEvent(element, events.end, keyCodes.end);
|
||||||
|
@ -199,7 +200,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addNextListener
|
* @method addNextListener
|
||||||
* @private
|
* @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) {
|
var addNextListener = function(element) {
|
||||||
// Left and right are flipped in RTL mode.
|
// Left and right are flipped in RTL mode.
|
||||||
|
@ -214,7 +215,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addPreviousListener
|
* @method addPreviousListener
|
||||||
* @private
|
* @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) {
|
var addPreviousListener = function(element) {
|
||||||
// Left and right are flipped in RTL mode.
|
// Left and right are flipped in RTL mode.
|
||||||
|
@ -229,7 +230,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addAsterixListener
|
* @method addAsterixListener
|
||||||
* @private
|
* @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) {
|
var addAsterixListener = function(element) {
|
||||||
addKeyboardEvent(element, events.asterix, keyCodes.asterix);
|
addKeyboardEvent(element, events.asterix, keyCodes.asterix);
|
||||||
|
@ -242,7 +243,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addScrollTopListener
|
* @method addScrollTopListener
|
||||||
* @private
|
* @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) {
|
var addScrollTopListener = function(element) {
|
||||||
element.off('scroll.cie.scrollTop').on('scroll.cie.scrollTop', function() {
|
element.off('scroll.cie.scrollTop').on('scroll.cie.scrollTop', function() {
|
||||||
|
@ -259,7 +260,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addScrollBottomListener
|
* @method addScrollBottomListener
|
||||||
* @private
|
* @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) {
|
var addScrollBottomListener = function(element) {
|
||||||
element.off('scroll.cie.scrollBottom').on('scroll.cie.scrollBottom', function() {
|
element.off('scroll.cie.scrollBottom').on('scroll.cie.scrollBottom', function() {
|
||||||
|
@ -279,7 +280,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addCtrlPageUpListener
|
* @method addCtrlPageUpListener
|
||||||
* @private
|
* @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) {
|
var addCtrlPageUpListener = function(element) {
|
||||||
element.off('keydown.cie.ctrlpageup').on('keydown.cie.ctrlpageup', function(e) {
|
element.off('keydown.cie.ctrlpageup').on('keydown.cie.ctrlpageup', function(e) {
|
||||||
|
@ -297,7 +298,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addCtrlPageDownListener
|
* @method addCtrlPageDownListener
|
||||||
* @private
|
* @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) {
|
var addCtrlPageDownListener = function(element) {
|
||||||
element.off('keydown.cie.ctrlpagedown').on('keydown.cie.ctrlpagedown', function(e) {
|
element.off('keydown.cie.ctrlpagedown').on('keydown.cie.ctrlpagedown', function(e) {
|
||||||
|
@ -315,7 +316,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method addEnterListener
|
* @method addEnterListener
|
||||||
* @private
|
* @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) {
|
var addEnterListener = function(element) {
|
||||||
addKeyboardEvent(element, events.enter, keyCodes.enter);
|
addKeyboardEvent(element, events.enter, keyCodes.enter);
|
||||||
|
@ -326,7 +327,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method getHandlers
|
* @method getHandlers
|
||||||
* @private
|
* @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 getHandlers = function() {
|
||||||
var handlers = {};
|
var handlers = {};
|
||||||
|
@ -355,7 +356,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) {
|
||||||
*
|
*
|
||||||
* @method define
|
* @method define
|
||||||
* @public
|
* @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
|
* @param {array} include the array of events to be triggered
|
||||||
*/
|
*/
|
||||||
var define = function(element, include) {
|
var define = function(element, include) {
|
||||||
|
|
|
@ -26,19 +26,19 @@
|
||||||
define(function() {
|
define(function() {
|
||||||
|
|
||||||
return /** @alias module:core/key_codes */ {
|
return /** @alias module:core/key_codes */ {
|
||||||
tab: 9,
|
'tab': 9,
|
||||||
enter: 13,
|
'enter': 13,
|
||||||
escape: 27,
|
'escape': 27,
|
||||||
space: 32,
|
'space': 32,
|
||||||
end: 35,
|
'end': 35,
|
||||||
home: 36,
|
'home': 36,
|
||||||
arrowLeft: 37,
|
'arrowLeft': 37,
|
||||||
arrowUp: 38,
|
'arrowUp': 38,
|
||||||
arrowRight: 39,
|
'arrowRight': 39,
|
||||||
arrowDown: 40,
|
'arrowDown': 40,
|
||||||
8: 56,
|
'8': 56,
|
||||||
asterix: 106,
|
'asterix': 106,
|
||||||
pageUp: 33,
|
'pageUp': 33,
|
||||||
pageDown: 34,
|
'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;
|
||||||
|
});
|
65
lib/templates/modal.mustache
Normal file
65
lib/templates/modal.mustache
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
{{!
|
||||||
|
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/>.
|
||||||
|
}}
|
||||||
|
{{!
|
||||||
|
@template core/modal
|
||||||
|
|
||||||
|
Moodle modal template.
|
||||||
|
|
||||||
|
The purpose of this template is to render a modal
|
||||||
|
|
||||||
|
Classes required for JS:
|
||||||
|
* none
|
||||||
|
|
||||||
|
Data attributes required for JS:
|
||||||
|
* none
|
||||||
|
|
||||||
|
Context variables required for this template:
|
||||||
|
* title A cleaned string (use clean_text()) to display.
|
||||||
|
* body HTML content for the boday
|
||||||
|
* footer HTML content for the footer
|
||||||
|
|
||||||
|
Example context (json):
|
||||||
|
{
|
||||||
|
"title": "Example modal",
|
||||||
|
"body": "Some example content for the body",
|
||||||
|
"footer": "Footer text, right here!",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
<div class="modal-container moodle-has-zindex hide" data-region="modal-container" aria-hidden="true" role="dialog">
|
||||||
|
<div class="modal {{$classes}}{{/classes}}"
|
||||||
|
data-region="modal"
|
||||||
|
aria-labelledby="{{uniqid}}-modal-title"
|
||||||
|
role="document">
|
||||||
|
<div class="modal-header" data-region="header">
|
||||||
|
<button type="button" class="close" data-action="hide" title="{{#str}} closebuttontitle {{/str}}"></button>
|
||||||
|
{{$header}}
|
||||||
|
<h3 id="{{uniqid}}-modal-title" data-region="title" tabindex="0">{{title}}</h3>
|
||||||
|
{{/header}}
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" data-region="body">
|
||||||
|
{{$body}}
|
||||||
|
{{{body}}}
|
||||||
|
{{/body}}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer" data-region="footer">
|
||||||
|
{{$footer}}
|
||||||
|
{{{footer}}}
|
||||||
|
{{/footer}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
36
lib/templates/modal_backdrop.mustache
Normal file
36
lib/templates/modal_backdrop.mustache
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{{!
|
||||||
|
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/>.
|
||||||
|
}}
|
||||||
|
{{!
|
||||||
|
@template core/modal_backdrop
|
||||||
|
|
||||||
|
Moodle modal backdrop template.
|
||||||
|
|
||||||
|
The purpose of this template is to render a modal
|
||||||
|
|
||||||
|
Classes required for JS:
|
||||||
|
* none
|
||||||
|
|
||||||
|
Data attributes required for JS:
|
||||||
|
* none
|
||||||
|
|
||||||
|
Context variables required for this template:
|
||||||
|
* none
|
||||||
|
|
||||||
|
Example context (json):
|
||||||
|
{}
|
||||||
|
}}
|
||||||
|
<div class="modal-backdrop hide" aria-hidden="true" data-region="modal-backdrop"></div>
|
46
lib/templates/modal_save_cancel.mustache
Normal file
46
lib/templates/modal_save_cancel.mustache
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{{!
|
||||||
|
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/>.
|
||||||
|
}}
|
||||||
|
{{!
|
||||||
|
@template core/modal
|
||||||
|
|
||||||
|
Moodle modal template with save and cancel buttons.
|
||||||
|
|
||||||
|
The purpose of this template is to render a modal.
|
||||||
|
|
||||||
|
Classes required for JS:
|
||||||
|
* none
|
||||||
|
|
||||||
|
Data attributes required for JS:
|
||||||
|
* none
|
||||||
|
|
||||||
|
Context variables required for this template:
|
||||||
|
* title A cleaned string (use clean_text()) to display.
|
||||||
|
* body HTML content for the boday
|
||||||
|
|
||||||
|
Example context (json):
|
||||||
|
{
|
||||||
|
"title": "Example save cancel modal",
|
||||||
|
"body": "Some example content for the body"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
{{< core/modal }}
|
||||||
|
{{$footer}}
|
||||||
|
<button type="button" class="btn btn-primary" data-action="save">{{#str}} savechanges {{/str}}</button>
|
||||||
|
<button type="button" class="btn" data-action="cancel">{{#str}} cancel {{/str}}</button>
|
||||||
|
{{/footer}}
|
||||||
|
{{/ core/modal }}
|
|
@ -52,7 +52,8 @@ $THEME->sheets = array(
|
||||||
'filemanager',
|
'filemanager',
|
||||||
'templates',
|
'templates',
|
||||||
'autocomplete',
|
'autocomplete',
|
||||||
'search'
|
'search',
|
||||||
|
'modal'
|
||||||
);
|
);
|
||||||
|
|
||||||
$THEME->editor_sheets = array('editor');
|
$THEME->editor_sheets = array('editor');
|
||||||
|
|
141
theme/base/style/modal.css
Normal file
141
theme/base/style/modal.css
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
body.modal-open {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 4050;
|
||||||
|
outline: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container.large .modal {
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container .modal {
|
||||||
|
position: relative;
|
||||||
|
margin: 50px auto 30px;
|
||||||
|
top: auto;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
left: auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 560px;
|
||||||
|
box-shadow: 5px 5px 20px 0 #666;
|
||||||
|
-webkit-box-shadow: 5px 5px 20px 0 #666;
|
||||||
|
-moz-box-shadow: 5px 5px 20px 0 #666;
|
||||||
|
border-radius: 10px;
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
background-color: #fff;
|
||||||
|
background-clip: padding-box;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container .modal .modal-header {
|
||||||
|
min-height: 13px;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 1px 1px 1px #fff;
|
||||||
|
border-bottom: 1px solid #bbb;
|
||||||
|
background: #ccc;
|
||||||
|
background-color: #ebebeb;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(to bottom, #fff, #ccc),
|
||||||
|
-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#ccc));
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
-webkit-border-radius: 10px 10px 0 0;
|
||||||
|
-moz-border-radius: 10px 10px 0 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container .modal .modal-header h3 {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container .modal .modal-header .close {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
width: 25px;
|
||||||
|
height: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 1;
|
||||||
|
background-image: url([[pix:theme|sprite]]);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-style: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.25) inset,
|
||||||
|
0 2px 0 rgba(255, 255, 255, 0.30) inset,
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
|
-webkit-box-shadow:
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.25) inset,
|
||||||
|
0 2px 0 rgba(255, 255, 255, 0.30) inset,
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
|
-moz-box-shadow:
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.25) inset,
|
||||||
|
0 2px 0 rgba(255, 255, 255, 0.30) inset,
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container .modal .modal-header .close:hover,
|
||||||
|
.modal-container .modal .modal-header .close:active,
|
||||||
|
.modal-container .modal .modal-header .close:focus {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container .modal .modal-body {
|
||||||
|
max-height: none;
|
||||||
|
position: relative;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container .modal .modal-footer {
|
||||||
|
border-top: 1px solid #bbb;
|
||||||
|
text-align: center;
|
||||||
|
padding: .7em 0;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
box-shadow: none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
-moz-box-shadow: none;
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
-webkit-border-radius: 0 0 10px 10px;
|
||||||
|
-moz-border-radius: 0 0 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
z-index: 4049;
|
||||||
|
background-color: #aaa;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-rtl .modal-container .modal .modal-header .close {
|
||||||
|
left: 5px;
|
||||||
|
right: auto;
|
||||||
|
}
|
|
@ -50,6 +50,7 @@ body {
|
||||||
// New Moodle stuff that builds on Bootstrap.
|
// New Moodle stuff that builds on Bootstrap.
|
||||||
@import "moodle/blocks";
|
@import "moodle/blocks";
|
||||||
@import "moodle/forms";
|
@import "moodle/forms";
|
||||||
|
@import "moodle/modal";
|
||||||
@import "moodle/modules";
|
@import "moodle/modules";
|
||||||
@import "moodle/chat";
|
@import "moodle/chat";
|
||||||
@import "moodle/reports";
|
@import "moodle/reports";
|
||||||
|
|
126
theme/bootstrapbase/less/moodle/modal.less
Normal file
126
theme/bootstrapbase/less/moodle/modal.less
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
body {
|
||||||
|
&.modal-open {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 4050;
|
||||||
|
outline: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
position: relative;
|
||||||
|
margin: 50px auto 30px;
|
||||||
|
top: auto;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
left: auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 560px;
|
||||||
|
box-shadow: 5px 5px 20px 0 #666;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
min-height: 13px;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 1px 1px 1px #fff;
|
||||||
|
border-bottom: 1px solid #bbb;
|
||||||
|
background: #ccc;
|
||||||
|
background-color: #ebebeb;
|
||||||
|
background-image: linear-gradient(to bottom, #fff, #ccc);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
width: 25px;
|
||||||
|
height: 15px;
|
||||||
|
line-height: 15px;
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 1;
|
||||||
|
background-image: url([[pix:theme|sprite]]);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-style: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.25) inset,
|
||||||
|
0 2px 0 rgba(255, 255, 255, 0.30) inset,
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
border-top: 1px solid #bbb;
|
||||||
|
text-align: center;
|
||||||
|
padding: .7em 0;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
.modal {
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
z-index: 4049;
|
||||||
|
background-color: #aaa;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-rtl {
|
||||||
|
.modal-container {
|
||||||
|
.modal {
|
||||||
|
.modal-header {
|
||||||
|
.close {
|
||||||
|
left: 5px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue