diff --git a/README.md b/README.md index 072022f..37f61ec 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ -Bootstrap Modal v2.1 +Bootstrap Modal v3.0.0 ============= +Fork notice +----------- + +This is a fork of [jschr/bootstrap-modal](https://github.com/jschr/bootstrap-modal) which intends to provide a Bootstrap 3 compatible branch. + +Original README +----------- + See live demo [here](http://jschr.github.com/bootstrap-modal/). -Extends Bootstrap's native modals to provide additional functionality. Introduces a **ModalManager** class that operates behind the scenes to handle multiple modals by listening on their events. +Extends Bootstrap's native modals to provide additional functionality. Introduces a **ModalManager** class that operates behind the scenes to handle multiple modals by listening on their events. A single ModalManager is created by default on body and can be accessed through the jQuery plugin interface. @@ -21,7 +29,7 @@ Overview + Load content via AJAX + Disable background scrolling -Installation +Installation ----------- + Include `css/bootstrap-modal.css` after the main bootstrap css files. + Include `js/bootstrap-modalmanager.js` and `js/bootstrap-modal.js` after the main bootstrap js files. @@ -101,16 +109,16 @@ The reason for doing this instead of just simply setting `overflow: hidden` when Constrain Modal to Window Size ----------- - + You can bind the the height of the modal body to the window with something like this: - + $.fn.modal.defaults.maxHeight = function(){ // subtract the height of the modal header and footer - return $(window).height() - 165; + return $(window).height() - 165; } - + **Note:** This will be overwritten by the responsiveness and is only set when the modal is displayed, not when the window is resized. - + Tab Index for Modal Forms ----------- You can use `data-tabindex` instead of the default `tabindex` to specify the tabindex within a modal. @@ -121,7 +129,7 @@ You can use `data-tabindex` instead of the default `tabindex` to specify the tab See the stackable example on the [demo](http://jschr.github.com/bootstrap-modal/) page for an example. - + diff --git a/js/bootstrap-modal.js b/js/bootstrap-modal.js index fbc540e..4b0f727 100644 --- a/js/bootstrap-modal.js +++ b/js/bootstrap-modal.js @@ -1,5 +1,5 @@ /* =========================================================== - * bootstrap-modal.js v2.1 + * bootstrap-modal.js v3.0.0 * =========================================================== * Copyright 2012 Jordan Schroter * @@ -19,355 +19,441 @@ !function ($) { - "use strict"; // jshint ;_; + "use strict"; // jshint ;_; - /* MODAL CLASS DEFINITION - * ====================== */ + /* MODAL CLASS DEFINITION + * ====================== */ - var Modal = function (element, options) { - this.init(element, options); - }; + var Modal = function (element, options) { + this.options = options + this.$element = $(element) + this.$backdrop = + this.isShown = null - Modal.prototype = { + if (this.options.remote) this.$element.load(this.options.remote) - constructor: Modal, + var manager = typeof this.options.manager === 'function' ? + this.options.manager.call(this) : this.options.manager; - init: function (element, options) { - this.options = options; + manager = manager.appendModal ? + manager : $(manager).modalmanager().data('modalmanager'); - this.$element = $(element) - .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)); + manager.appendModal(this); + } - this.options.remote && this.$element.find('.modal-body').load(this.options.remote); + Modal.DEFAULTS = { + keyboard: true, + backdrop: true, + loading: false, + show: true, + width: null, + height: null, + maxHeight: null, + modalOverflow: false, + consumeTab: true, + focusOn: null, + replace: false, + resize: false, + attentionAnimation: 'shake', + manager: 'body', + spinner: '
' + } - var manager = typeof this.options.manager === 'function' ? - this.options.manager.call(this) : this.options.manager; + Modal.prototype.toggle = function (_relatedTarget) { + return this[!this.isShown ? 'show' : 'hide'](_relatedTarget) + } - manager = manager.appendModal ? - manager : $(manager).modalmanager().data('modalmanager'); + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) - manager.appendModal(this); - }, + this.$element.trigger(e) - toggle: function () { - return this[!this.isShown ? 'show' : 'hide'](); - }, + if (this.isShown || e.isDefaultPrevented()) return - show: function () { - var e = $.Event('show'); + this.isShown = true - if (this.isShown) return; + this.escape() - this.$element.trigger(e); + this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) - if (e.isDefaultPrevented()) return; + this.tab(); - this.escape(); + this.options.loading && this.loading(); - this.tab(); + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') - this.options.loading && this.loading(); - }, + if (!that.$element.parent().length) { + that.$element.appendTo(document.body) // don't move modals dom position + } - hide: function (e) { - e && e.preventDefault(); + that.$element.show() - e = $.Event('hide'); + if (transition) { + that.$element[0].offsetWidth // force reflow + } - this.$element.trigger(e); + that.$element + .addClass('in') + .attr('aria-hidden', false) - if (!this.isShown || e.isDefaultPrevented()) return (this.isShown = false); + that.enforceFocus() - this.isShown = false; + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$element.find('.modal-dialog') // wait for modal to slide in + .one($.support.transition.end, function () { + that.$element.focus().trigger(e) + }) + .emulateTransitionEnd(300) : + that.$element.focus().trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + + this.tab(); + this.isLoading && this.loading(); + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .removeClass('animated') + .removeClass(this.options.attentionAnimation) + .removeClass('modal-overflow') + .attr('aria-hidden', true) + .off('click.dismiss.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one($.support.transition.end, $.proxy(this.hideModal, this)) + .emulateTransitionEnd(300) : + this.hideModal() + } + + Modal.prototype.layout = function() { + var prop = this.options.height ? 'height' : 'max-height', + value = this.options.height || this.options.maxHeight; + + if (this.options.width){ + this.$element.css('width', this.options.width); + + var that = this; + this.$element.css('margin-left', function () { + if (/%/ig.test(that.options.width)){ + return -(parseInt(that.options.width) / 2) + '%'; + } else { + return -($(this).width() / 2) + 'px'; + } + }); + } else { + this.$element.css('width', ''); + this.$element.css('margin-left', ''); + } + + this.$element.find('.modal-body') + .css('overflow', '') + .css(prop, ''); + + if (value){ + this.$element.find('.modal-body') + .css('overflow', 'auto') + .css(prop, value); + } + + var modalOverflow = $(window).height() - 10 < this.$element.height(); + + if (modalOverflow || this.options.modalOverflow) { + this.$element + .css('margin-top', 0) + .addClass('modal-overflow'); + } else { + this.$element + .css('margin-top', 0 - this.$element.height() / 2) + .removeClass('modal-overflow'); + } + } + + Modal.prototype.tab = function() { + var that = this; + + if (this.isShown && this.options.consumeTab) { + this.$element.on('keydown.tabindex.modal', '[data-tabindex]', function (e) { + if (e.keyCode && e.keyCode == 9){ + var $next = $(this), + $rollover = $(this); + + that.$element.find('[data-tabindex]:enabled:not([readonly])').each(function (e) { + if (!e.shiftKey){ + $next = $next.data('tabindex') < $(this).data('tabindex') ? + $next = $(this) : + $rollover = $(this); + } else { + $next = $next.data('tabindex') > $(this).data('tabindex') ? + $next = $(this) : + $rollover = $(this); + } + }); + + $next[0] !== $(this)[0] ? + $next.focus() : $rollover.focus(); + + e.preventDefault(); + } + }); + } else if (!this.isShown) { + this.$element.off('keydown.tabindex.modal'); + } + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { + this.$element.focus() + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + if (!this.$element.attr('tabindex')) this.$element.attr('tabindex', -1); + this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keyup.dismiss.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + + var prop = this.options.height ? 'height' : 'max-height'; + var value = this.options.height || this.options.maxHeight; + + if (value) { + this.$element.find('.modal-body') + .css('overflow', '') + .css(prop, ''); + } + + var that = this + this.$element.hide() + this.backdrop(function () { + that.removeBackdrop() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeLoading = function () { + this.$loading.remove(); + this.$loading = null; + this.isLoading = false; + } + + Modal.prototype.loading = function (callback) { + callback = callback || function () {}; + + var animate = this.$element.hasClass('fade') ? 'fade' : ''; + + if (!this.isLoading) { + var doAnimate = $.support.transition && animate; + + this.$loading = $('
') + .append(this.options.spinner) + .appendTo(this.$element); + + if (doAnimate) this.$loading[0].offsetWidth; // force reflow + + this.$loading.addClass('in'); + + this.isLoading = true; + + doAnimate ? + this.$loading.one($.support.transition.end, callback) : + callback(); + + } else if (this.isLoading && this.$loading) { + this.$loading.removeClass('in'); + + var that = this; + $.support.transition && this.$element.hasClass('fade')? + this.$loading.one($.support.transition.end, function () { that.removeLoading() }) : + that.removeLoading(); + + } else if (callback) { + callback(this.isLoading); + } + } + + Modal.prototype.focus = function () { + var $focusElem = this.$element.find(this.options.focusOn); + + $focusElem = $focusElem.length ? $focusElem : this.$element; + + $focusElem.focus(); + } + + Modal.prototype.attention = function () { + // NOTE: transitionEnd with keyframes causes odd behaviour + + if (this.options.attentionAnimation){ + this.$element + .removeClass('animated') + .removeClass(this.options.attentionAnimation); + + var that = this; + + setTimeout(function () { + that.$element + .addClass('animated') + .addClass(that.options.attentionAnimation); + }, 0); + } - this.escape(); - this.tab(); + this.focus(); + } - this.isLoading && this.loading(); + Modal.prototype.destroy = function () { + var e = $.Event('destroy.bs.modal'); + this.$element.trigger(e); + if (e.isDefaultPrevented()) return; - $(document).off('focusin.modal'); + this.teardown(); + } - this.$element - .removeClass('in') - .removeClass('animated') - .removeClass(this.options.attentionAnimation) - .removeClass('modal-overflow') - .attr('aria-hidden', true); + Modal.prototype.teardown = function () { + if (!this.$parent.length){ + this.$element.remove(); + this.$element = null; + return; + } - $.support.transition && this.$element.hasClass('fade') ? - this.hideWithTransition() : - this.hideModal(); - }, + if (this.$parent !== this.$element.parent()){ + this.$element.appendTo(this.$parent); + } - layout: function () { - var prop = this.options.height ? 'height' : 'max-height', - value = this.options.height || this.options.maxHeight; + this.$element.off('.modal'); + this.$element.removeData('modal'); + this.$element + .removeClass('in') + .attr('aria-hidden', true); + } - if (this.options.width){ - this.$element.css('width', this.options.width); - var that = this; - this.$element.css('margin-left', function () { - if (/%/ig.test(that.options.width)){ - return -(parseInt(that.options.width) / 2) + '%'; - } else { - return -($(this).width() / 2) + 'px'; - } - }); - } else { - this.$element.css('width', ''); - this.$element.css('margin-left', ''); - } - - this.$element.find('.modal-body') - .css('overflow', '') - .css(prop, ''); - - if (value){ - this.$element.find('.modal-body') - .css('overflow', 'auto') - .css(prop, value); - } - - var modalOverflow = $(window).height() - 10 < this.$element.height(); - - if (modalOverflow || this.options.modalOverflow) { - this.$element - .css('margin-top', 0) - .addClass('modal-overflow'); - } else { - this.$element - .css('margin-top', 0 - this.$element.height() / 2) - .removeClass('modal-overflow'); - } - }, - - tab: function () { - var that = this; - - if (this.isShown && this.options.consumeTab) { - this.$element.on('keydown.tabindex.modal', '[data-tabindex]', function (e) { - if (e.keyCode && e.keyCode == 9){ - var $next = $(this), - $rollover = $(this); - - that.$element.find('[data-tabindex]:enabled:not([readonly])').each(function (e) { - if (!e.shiftKey){ - $next = $next.data('tabindex') < $(this).data('tabindex') ? - $next = $(this) : - $rollover = $(this); - } else { - $next = $next.data('tabindex') > $(this).data('tabindex') ? - $next = $(this) : - $rollover = $(this); - } - }); - - $next[0] !== $(this)[0] ? - $next.focus() : $rollover.focus(); - - e.preventDefault(); - } - }); - } else if (!this.isShown) { - this.$element.off('keydown.tabindex.modal'); - } - }, - - escape: function () { - var that = this; - if (this.isShown && this.options.keyboard) { - if (!this.$element.attr('tabindex')) this.$element.attr('tabindex', -1); - - this.$element.on('keyup.dismiss.modal', function (e) { - e.which == 27 && that.hide(); - }); - } else if (!this.isShown) { - this.$element.off('keyup.dismiss.modal') - } - }, - - hideWithTransition: function () { - var that = this - , timeout = setTimeout(function () { - that.$element.off($.support.transition.end); - that.hideModal(); - }, 500); - - this.$element.one($.support.transition.end, function () { - clearTimeout(timeout); - that.hideModal(); - }); - }, - - hideModal: function () { - var prop = this.options.height ? 'height' : 'max-height'; - var value = this.options.height || this.options.maxHeight; - - if (value){ - this.$element.find('.modal-body') - .css('overflow', '') - .css(prop, ''); - } - - this.$element - .hide() - .trigger('hidden'); - }, - - removeLoading: function () { - this.$loading.remove(); - this.$loading = null; - this.isLoading = false; - }, - - loading: function (callback) { - callback = callback || function () {}; - - var animate = this.$element.hasClass('fade') ? 'fade' : ''; - - if (!this.isLoading) { - var doAnimate = $.support.transition && animate; - - this.$loading = $('
') - .append(this.options.spinner) - .appendTo(this.$element); - - if (doAnimate) this.$loading[0].offsetWidth; // force reflow - - this.$loading.addClass('in'); - - this.isLoading = true; - - doAnimate ? - this.$loading.one($.support.transition.end, callback) : - callback(); - - } else if (this.isLoading && this.$loading) { - this.$loading.removeClass('in'); - - var that = this; - $.support.transition && this.$element.hasClass('fade')? - this.$loading.one($.support.transition.end, function () { that.removeLoading() }) : - that.removeLoading(); - - } else if (callback) { - callback(this.isLoading); - } - }, - - focus: function () { - var $focusElem = this.$element.find(this.options.focusOn); - - $focusElem = $focusElem.length ? $focusElem : this.$element; - - $focusElem.focus(); - }, - - attention: function (){ - // NOTE: transitionEnd with keyframes causes odd behaviour - - if (this.options.attentionAnimation){ - this.$element - .removeClass('animated') - .removeClass(this.options.attentionAnimation); - - var that = this; - - setTimeout(function () { - that.$element - .addClass('animated') - .addClass(that.options.attentionAnimation); - }, 0); - } - - - this.focus(); - }, - - - destroy: function () { - var e = $.Event('destroy'); - this.$element.trigger(e); - if (e.isDefaultPrevented()) return; - - this.teardown(); - }, - - teardown: function () { - if (!this.$parent.length){ - this.$element.remove(); - this.$element = null; - return; - } - - if (this.$parent !== this.$element.parent()){ - this.$element.appendTo(this.$parent); - } - - this.$element.off('.modal'); - this.$element.removeData('modal'); - this.$element - .removeClass('in') - .attr('aria-hidden', true); - } - }; - - - /* MODAL PLUGIN DEFINITION - * ======================= */ - - $.fn.modal = function (option, args) { - return this.each(function () { - var $this = $(this), - data = $this.data('modal'), - options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option); - - if (!data) $this.data('modal', (data = new Modal(this, options))); - if (typeof option == 'string') data[option].apply(data, [].concat(args)); - else if (options.show) data.show() - }) - }; - - $.fn.modal.defaults = { - keyboard: true, - backdrop: true, - loading: false, - show: true, - width: null, - height: null, - maxHeight: null, - modalOverflow: false, - consumeTab: true, - focusOn: null, - replace: false, - resize: false, - attentionAnimation: 'shake', - manager: 'body', - spinner: '
' - }; - - $.fn.modal.Constructor = Modal; - - - /* MODAL DATA-API - * ============== */ - - $(function () { - $(document).off('click.modal').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { - var $this = $(this), - href = $this.attr('href'), - $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))), //strip for ie7 - option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()); - - e.preventDefault(); - $target - .modal(option) - .one('hide', function () { - $this.focus(); - }) - }); - }); + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('