MDL-55727 javascript: Add modal module

This commit is contained in:
Ryan Wyllie 2016-08-23 03:33:31 +00:00
parent e845b96b83
commit 2bcef5594a
22 changed files with 1475 additions and 35 deletions

View 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
View 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

File diff suppressed because one or more lines are too long

1
lib/amd/build/modal_backdrop.min.js vendored Normal file
View 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
View 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
View 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}});

View 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});

View file

@ -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) {

View file

@ -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
View 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;
});

View 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;
});

View 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',
};
});

View 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,
};
});

View 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;
});

View 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>

View 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>

View 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 }}

View file

@ -52,7 +52,8 @@ $THEME->sheets = array(
'filemanager',
'templates',
'autocomplete',
'search'
'search',
'modal'
);
$THEME->editor_sheets = array('editor');

141
theme/base/style/modal.css Normal file
View 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;
}

View file

@ -50,6 +50,7 @@ body {
// New Moodle stuff that builds on Bootstrap.
@import "moodle/blocks";
@import "moodle/forms";
@import "moodle/modal";
@import "moodle/modules";
@import "moodle/chat";
@import "moodle/reports";

View 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