bootstrap-modalmanager.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. /* ===========================================================
  2. * bootstrap-modalmanager.js v2.1
  3. * ===========================================================
  4. * Copyright 2012 Jordan Schroter.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. * ========================================================== */
  18. !function ($) {
  19. "use strict"; // jshint ;_;
  20. /* MODAL MANAGER CLASS DEFINITION
  21. * ====================== */
  22. var ModalManager = function (element, options) {
  23. this.init(element, options);
  24. };
  25. ModalManager.prototype = {
  26. constructor: ModalManager,
  27. init: function (element, options) {
  28. this.$element = $(element);
  29. this.options = $.extend({}, $.fn.modalmanager.defaults, this.$element.data(), typeof options == 'object' && options);
  30. this.stack = [];
  31. this.backdropCount = 0;
  32. if (this.options.resize) {
  33. var resizeTimeout,
  34. that = this;
  35. $(window).on('resize.modal', function(){
  36. resizeTimeout && clearTimeout(resizeTimeout);
  37. resizeTimeout = setTimeout(function(){
  38. for (var i = 0; i < that.stack.length; i++){
  39. that.stack[i].isShown && that.stack[i].layout();
  40. }
  41. }, 10);
  42. });
  43. }
  44. },
  45. createModal: function (element, options) {
  46. $(element).modal($.extend({ manager: this }, options));
  47. },
  48. appendModal: function (modal) {
  49. this.stack.push(modal);
  50. var that = this;
  51. modal.$element.on('show.modalmanager', targetIsSelf(function (e) {
  52. var showModal = function(){
  53. modal.isShown = true;
  54. var transition = $.support.transition && modal.$element.hasClass('fade');
  55. that.$element
  56. .toggleClass('modal-open', that.hasOpenModal())
  57. .toggleClass('page-overflow', $(window).height() < that.$element.height());
  58. modal.$parent = modal.$element.parent();
  59. modal.$container = that.createContainer(modal);
  60. modal.$element.appendTo(modal.$container);
  61. that.backdrop(modal, function () {
  62. modal.$element.show();
  63. if (transition) {
  64. //modal.$element[0].style.display = 'run-in';
  65. modal.$element[0].offsetWidth;
  66. //modal.$element.one($.support.transition.end, function () { modal.$element[0].style.display = 'block' });
  67. }
  68. modal.layout();
  69. modal.$element
  70. .addClass('in')
  71. .attr('aria-hidden', false);
  72. var complete = function () {
  73. that.setFocus();
  74. modal.$element.trigger('shown');
  75. };
  76. transition ?
  77. modal.$element.one($.support.transition.end, complete) :
  78. complete();
  79. });
  80. };
  81. modal.options.replace ?
  82. that.replace(showModal) :
  83. showModal();
  84. }));
  85. modal.$element.on('hidden.modalmanager', targetIsSelf(function (e) {
  86. that.backdrop(modal);
  87. if (modal.$backdrop){
  88. $.support.transition && modal.$element.hasClass('fade') ?
  89. modal.$backdrop.one($.support.transition.end, function () { that.destroyModal(modal) }) :
  90. that.destroyModal(modal);
  91. } else {
  92. that.destroyModal(modal);
  93. }
  94. }));
  95. modal.$element.on('destroy.modalmanager', targetIsSelf(function (e) {
  96. that.removeModal(modal);
  97. }));
  98. },
  99. destroyModal: function (modal) {
  100. modal.destroy();
  101. var hasOpenModal = this.hasOpenModal();
  102. this.$element.toggleClass('modal-open', hasOpenModal);
  103. if (!hasOpenModal){
  104. this.$element.removeClass('page-overflow');
  105. }
  106. this.removeContainer(modal);
  107. this.setFocus();
  108. },
  109. hasOpenModal: function () {
  110. for (var i = 0; i < this.stack.length; i++){
  111. if (this.stack[i].isShown) return true;
  112. }
  113. return false;
  114. },
  115. setFocus: function () {
  116. var topModal;
  117. for (var i = 0; i < this.stack.length; i++){
  118. if (this.stack[i].isShown) topModal = this.stack[i];
  119. }
  120. if (!topModal) return;
  121. topModal.focus();
  122. },
  123. removeModal: function (modal) {
  124. modal.$element.off('.modalmanager');
  125. if (modal.$backdrop) this.removeBackdrop(modal);
  126. this.stack.splice(this.getIndexOfModal(modal), 1);
  127. },
  128. getModalAt: function (index) {
  129. return this.stack[index];
  130. },
  131. getIndexOfModal: function (modal) {
  132. for (var i = 0; i < this.stack.length; i++){
  133. if (modal === this.stack[i]) return i;
  134. }
  135. },
  136. replace: function (callback) {
  137. var topModal;
  138. for (var i = 0; i < this.stack.length; i++){
  139. if (this.stack[i].isShown) topModal = this.stack[i];
  140. }
  141. if (topModal) {
  142. this.$backdropHandle = topModal.$backdrop;
  143. topModal.$backdrop = null;
  144. callback && topModal.$element.one('hidden',
  145. targetIsSelf( $.proxy(callback, this) ));
  146. topModal.hide();
  147. } else if (callback) {
  148. callback();
  149. }
  150. },
  151. removeBackdrop: function (modal) {
  152. modal.$backdrop.remove();
  153. modal.$backdrop = null;
  154. },
  155. createBackdrop: function (animate) {
  156. var $backdrop;
  157. if (!this.$backdropHandle) {
  158. $backdrop = $('<div class="modal-backdrop ' + animate + '" />')
  159. .appendTo(this.$element);
  160. } else {
  161. $backdrop = this.$backdropHandle;
  162. $backdrop.off('.modalmanager');
  163. this.$backdropHandle = null;
  164. this.isLoading && this.removeSpinner();
  165. }
  166. return $backdrop;
  167. },
  168. removeContainer: function (modal) {
  169. modal.$container.remove();
  170. modal.$container = null;
  171. },
  172. createContainer: function (modal) {
  173. var $container;
  174. $container = $('<div class="modal-scrollable">')
  175. .css('z-index', getzIndex( 'modal',
  176. modal ? this.getIndexOfModal(modal) : this.stack.length ))
  177. .appendTo(this.$element);
  178. if (modal && modal.options.backdrop != 'static') {
  179. $container.on('click.modal', targetIsSelf(function (e) {
  180. modal.hide();
  181. }));
  182. } else if (modal) {
  183. $container.on('click.modal', targetIsSelf(function (e) {
  184. modal.attention();
  185. }));
  186. }
  187. return $container;
  188. },
  189. backdrop: function (modal, callback) {
  190. var animate = modal.$element.hasClass('fade') ? 'fade' : '',
  191. showBackdrop = modal.options.backdrop &&
  192. this.backdropCount < this.options.backdropLimit;
  193. if (modal.isShown && showBackdrop) {
  194. var doAnimate = $.support.transition && animate && !this.$backdropHandle;
  195. modal.$backdrop = this.createBackdrop(animate);
  196. modal.$backdrop.css('z-index', getzIndex( 'backdrop', this.getIndexOfModal(modal) ));
  197. if (doAnimate) modal.$backdrop[0].offsetWidth; // force reflow
  198. modal.$backdrop.addClass('in');
  199. this.backdropCount += 1;
  200. doAnimate ?
  201. modal.$backdrop.one($.support.transition.end, callback) :
  202. callback();
  203. } else if (!modal.isShown && modal.$backdrop) {
  204. modal.$backdrop.removeClass('in');
  205. this.backdropCount -= 1;
  206. var that = this;
  207. $.support.transition && modal.$element.hasClass('fade')?
  208. modal.$backdrop.one($.support.transition.end, function () { that.removeBackdrop(modal) }) :
  209. that.removeBackdrop(modal);
  210. } else if (callback) {
  211. callback();
  212. }
  213. },
  214. removeSpinner: function(){
  215. this.$spinner && this.$spinner.remove();
  216. this.$spinner = null;
  217. this.isLoading = false;
  218. },
  219. removeLoading: function () {
  220. this.$backdropHandle && this.$backdropHandle.remove();
  221. this.$backdropHandle = null;
  222. this.removeSpinner();
  223. },
  224. loading: function (callback) {
  225. callback = callback || function () { };
  226. this.$element
  227. .toggleClass('modal-open', !this.isLoading || this.hasOpenModal())
  228. .toggleClass('page-overflow', $(window).height() < this.$element.height());
  229. if (!this.isLoading) {
  230. this.$backdropHandle = this.createBackdrop('fade');
  231. this.$backdropHandle[0].offsetWidth; // force reflow
  232. this.$backdropHandle
  233. .css('z-index', getzIndex('backdrop', this.stack.length))
  234. .addClass('in');
  235. var $spinner = $(this.options.spinner)
  236. .css('z-index', getzIndex('modal', this.stack.length))
  237. .appendTo(this.$element)
  238. .addClass('in');
  239. this.$spinner = $(this.createContainer())
  240. .append($spinner)
  241. .on('click.modalmanager', $.proxy(this.loading, this));
  242. this.isLoading = true;
  243. $.support.transition ?
  244. this.$backdropHandle.one($.support.transition.end, callback) :
  245. callback();
  246. } else if (this.isLoading && this.$backdropHandle) {
  247. this.$backdropHandle.removeClass('in');
  248. var that = this;
  249. $.support.transition ?
  250. this.$backdropHandle.one($.support.transition.end, function () { that.removeLoading() }) :
  251. that.removeLoading();
  252. } else if (callback) {
  253. callback(this.isLoading);
  254. }
  255. }
  256. };
  257. /* PRIVATE METHODS
  258. * ======================= */
  259. // computes and caches the zindexes
  260. var getzIndex = (function () {
  261. var zIndexFactor,
  262. baseIndex = {};
  263. return function (type, pos) {
  264. if (typeof zIndexFactor === 'undefined'){
  265. var $baseModal = $('<div class="modal hide" />').appendTo('body'),
  266. $baseBackdrop = $('<div class="modal-backdrop hide" />').appendTo('body');
  267. baseIndex['modal'] = +$baseModal.css('z-index');
  268. baseIndex['backdrop'] = +$baseBackdrop.css('z-index');
  269. zIndexFactor = baseIndex['modal'] - baseIndex['backdrop'];
  270. $baseModal.remove();
  271. $baseBackdrop.remove();
  272. $baseBackdrop = $baseModal = null;
  273. }
  274. return baseIndex[type] + (zIndexFactor * pos);
  275. }
  276. }());
  277. // make sure the event target is the modal itself in order to prevent
  278. // other components such as tabsfrom triggering the modal manager.
  279. // if Boostsrap namespaced events, this would not be needed.
  280. function targetIsSelf(callback){
  281. return function (e) {
  282. if (this === e.target){
  283. return callback.apply(this, arguments);
  284. }
  285. }
  286. }
  287. /* MODAL MANAGER PLUGIN DEFINITION
  288. * ======================= */
  289. $.fn.modalmanager = function (option, args) {
  290. return this.each(function () {
  291. var $this = $(this),
  292. data = $this.data('modalmanager');
  293. if (!data) $this.data('modalmanager', (data = new ModalManager(this, option)));
  294. if (typeof option === 'string') data[option].apply(data, [].concat(args))
  295. })
  296. };
  297. $.fn.modalmanager.defaults = {
  298. backdropLimit: 999,
  299. resize: true,
  300. spinner: '<div class="loading-spinner fade" style="width: 200px; margin-left: -100px;"><div class="progress progress-striped active"><div class="bar" style="width: 100%;"></div></div></div>'
  301. };
  302. $.fn.modalmanager.Constructor = ModalManager
  303. }(jQuery);