You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

892 lines
26 KiB

  1. /*!
  2. * # Semantic UI 2.1.6 - Modal
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2015 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. "use strict";
  13. $.fn.modal = function(parameters) {
  14. var
  15. $allModules = $(this),
  16. $window = $(window),
  17. $document = $(document),
  18. $body = $('body'),
  19. moduleSelector = $allModules.selector || '',
  20. time = new Date().getTime(),
  21. performance = [],
  22. query = arguments[0],
  23. methodInvoked = (typeof query == 'string'),
  24. queryArguments = [].slice.call(arguments, 1),
  25. requestAnimationFrame = window.requestAnimationFrame
  26. || window.mozRequestAnimationFrame
  27. || window.webkitRequestAnimationFrame
  28. || window.msRequestAnimationFrame
  29. || function(callback) { setTimeout(callback, 0); },
  30. returnedValue
  31. ;
  32. $allModules
  33. .each(function() {
  34. var
  35. settings = ( $.isPlainObject(parameters) )
  36. ? $.extend(true, {}, $.fn.modal.settings, parameters)
  37. : $.extend({}, $.fn.modal.settings),
  38. selector = settings.selector,
  39. className = settings.className,
  40. namespace = settings.namespace,
  41. error = settings.error,
  42. eventNamespace = '.' + namespace,
  43. moduleNamespace = 'module-' + namespace,
  44. $module = $(this),
  45. $context = $(settings.context),
  46. $close = $module.find(selector.close),
  47. $allModals,
  48. $otherModals,
  49. $focusedElement,
  50. $dimmable,
  51. $dimmer,
  52. element = this,
  53. instance = $module.data(moduleNamespace),
  54. elementNamespace,
  55. id,
  56. observer,
  57. module
  58. ;
  59. module = {
  60. initialize: function() {
  61. module.verbose('Initializing dimmer', $context);
  62. module.create.id();
  63. module.create.dimmer();
  64. module.refreshModals();
  65. module.bind.events();
  66. if(settings.observeChanges) {
  67. module.observeChanges();
  68. }
  69. module.instantiate();
  70. },
  71. instantiate: function() {
  72. module.verbose('Storing instance of modal');
  73. instance = module;
  74. $module
  75. .data(moduleNamespace, instance)
  76. ;
  77. },
  78. create: {
  79. dimmer: function() {
  80. var
  81. defaultSettings = {
  82. debug : settings.debug,
  83. dimmerName : 'modals',
  84. duration : {
  85. show : settings.duration,
  86. hide : settings.duration
  87. }
  88. },
  89. dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings)
  90. ;
  91. if(settings.inverted) {
  92. dimmerSettings.variation = (dimmerSettings.variation !== undefined)
  93. ? dimmerSettings.variation + ' inverted'
  94. : 'inverted'
  95. ;
  96. }
  97. if($.fn.dimmer === undefined) {
  98. module.error(error.dimmer);
  99. return;
  100. }
  101. module.debug('Creating dimmer with settings', dimmerSettings);
  102. $dimmable = $context.dimmer(dimmerSettings);
  103. if(settings.detachable) {
  104. module.verbose('Modal is detachable, moving content into dimmer');
  105. $dimmable.dimmer('add content', $module);
  106. }
  107. else {
  108. module.set.undetached();
  109. }
  110. if(settings.blurring) {
  111. $dimmable.addClass(className.blurring);
  112. }
  113. $dimmer = $dimmable.dimmer('get dimmer');
  114. },
  115. id: function() {
  116. id = (Math.random().toString(16) + '000000000').substr(2,8);
  117. elementNamespace = '.' + id;
  118. module.verbose('Creating unique id for element', id);
  119. }
  120. },
  121. destroy: function() {
  122. module.verbose('Destroying previous modal');
  123. $module
  124. .removeData(moduleNamespace)
  125. .off(eventNamespace)
  126. ;
  127. $window.off(elementNamespace);
  128. $close.off(eventNamespace);
  129. $context.dimmer('destroy');
  130. },
  131. observeChanges: function() {
  132. if('MutationObserver' in window) {
  133. observer = new MutationObserver(function(mutations) {
  134. module.debug('DOM tree modified, refreshing');
  135. module.refresh();
  136. });
  137. observer.observe(element, {
  138. childList : true,
  139. subtree : true
  140. });
  141. module.debug('Setting up mutation observer', observer);
  142. }
  143. },
  144. refresh: function() {
  145. module.remove.scrolling();
  146. module.cacheSizes();
  147. module.set.screenHeight();
  148. module.set.type();
  149. module.set.position();
  150. },
  151. refreshModals: function() {
  152. $otherModals = $module.siblings(selector.modal);
  153. $allModals = $otherModals.add($module);
  154. },
  155. attachEvents: function(selector, event) {
  156. var
  157. $toggle = $(selector)
  158. ;
  159. event = $.isFunction(module[event])
  160. ? module[event]
  161. : module.toggle
  162. ;
  163. if($toggle.length > 0) {
  164. module.debug('Attaching modal events to element', selector, event);
  165. $toggle
  166. .off(eventNamespace)
  167. .on('click' + eventNamespace, event)
  168. ;
  169. }
  170. else {
  171. module.error(error.notFound, selector);
  172. }
  173. },
  174. bind: {
  175. events: function() {
  176. module.verbose('Attaching events');
  177. $module
  178. .on('click' + eventNamespace, selector.close, module.event.close)
  179. .on('click' + eventNamespace, selector.approve, module.event.approve)
  180. .on('click' + eventNamespace, selector.deny, module.event.deny)
  181. ;
  182. $window
  183. .on('resize' + elementNamespace, module.event.resize)
  184. ;
  185. }
  186. },
  187. get: {
  188. id: function() {
  189. return (Math.random().toString(16) + '000000000').substr(2,8);
  190. }
  191. },
  192. event: {
  193. approve: function() {
  194. if(settings.onApprove.call(element, $(this)) === false) {
  195. module.verbose('Approve callback returned false cancelling hide');
  196. return;
  197. }
  198. module.hide();
  199. },
  200. deny: function() {
  201. if(settings.onDeny.call(element, $(this)) === false) {
  202. module.verbose('Deny callback returned false cancelling hide');
  203. return;
  204. }
  205. module.hide();
  206. },
  207. close: function() {
  208. module.hide();
  209. },
  210. click: function(event) {
  211. var
  212. $target = $(event.target),
  213. isInModal = ($target.closest(selector.modal).length > 0),
  214. isInDOM = $.contains(document.documentElement, event.target)
  215. ;
  216. if(!isInModal && isInDOM) {
  217. module.debug('Dimmer clicked, hiding all modals');
  218. if( module.is.active() ) {
  219. module.remove.clickaway();
  220. if(settings.allowMultiple) {
  221. module.hide();
  222. }
  223. else {
  224. module.hideAll();
  225. }
  226. }
  227. }
  228. },
  229. debounce: function(method, delay) {
  230. clearTimeout(module.timer);
  231. module.timer = setTimeout(method, delay);
  232. },
  233. keyboard: function(event) {
  234. var
  235. keyCode = event.which,
  236. escapeKey = 27
  237. ;
  238. if(keyCode == escapeKey) {
  239. if(settings.closable) {
  240. module.debug('Escape key pressed hiding modal');
  241. module.hide();
  242. }
  243. else {
  244. module.debug('Escape key pressed, but closable is set to false');
  245. }
  246. event.preventDefault();
  247. }
  248. },
  249. resize: function() {
  250. if( $dimmable.dimmer('is active') ) {
  251. requestAnimationFrame(module.refresh);
  252. }
  253. }
  254. },
  255. toggle: function() {
  256. if( module.is.active() || module.is.animating() ) {
  257. module.hide();
  258. }
  259. else {
  260. module.show();
  261. }
  262. },
  263. show: function(callback) {
  264. callback = $.isFunction(callback)
  265. ? callback
  266. : function(){}
  267. ;
  268. module.refreshModals();
  269. module.showModal(callback);
  270. },
  271. hide: function(callback) {
  272. callback = $.isFunction(callback)
  273. ? callback
  274. : function(){}
  275. ;
  276. module.refreshModals();
  277. module.hideModal(callback);
  278. },
  279. showModal: function(callback) {
  280. callback = $.isFunction(callback)
  281. ? callback
  282. : function(){}
  283. ;
  284. if( module.is.animating() || !module.is.active() ) {
  285. module.showDimmer();
  286. module.cacheSizes();
  287. module.set.position();
  288. module.set.screenHeight();
  289. module.set.type();
  290. module.set.clickaway();
  291. if( !settings.allowMultiple && module.others.active() ) {
  292. module.hideOthers(module.showModal);
  293. }
  294. else {
  295. settings.onShow.call(element);
  296. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  297. module.debug('Showing modal with css animations');
  298. $module
  299. .transition({
  300. debug : settings.debug,
  301. animation : settings.transition + ' in',
  302. queue : settings.queue,
  303. duration : settings.duration,
  304. useFailSafe : true,
  305. onComplete : function() {
  306. settings.onVisible.apply(element);
  307. module.add.keyboardShortcuts();
  308. module.save.focus();
  309. module.set.active();
  310. if(settings.autofocus) {
  311. module.set.autofocus();
  312. }
  313. callback();
  314. }
  315. })
  316. ;
  317. }
  318. else {
  319. module.error(error.noTransition);
  320. }
  321. }
  322. }
  323. else {
  324. module.debug('Modal is already visible');
  325. }
  326. },
  327. hideModal: function(callback, keepDimmed) {
  328. callback = $.isFunction(callback)
  329. ? callback
  330. : function(){}
  331. ;
  332. module.debug('Hiding modal');
  333. if(settings.onHide.call(element, $(this)) === false) {
  334. module.verbose('Hide callback returned false cancelling hide');
  335. return;
  336. }
  337. if( module.is.animating() || module.is.active() ) {
  338. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  339. module.remove.active();
  340. $module
  341. .transition({
  342. debug : settings.debug,
  343. animation : settings.transition + ' out',
  344. queue : settings.queue,
  345. duration : settings.duration,
  346. useFailSafe : true,
  347. onStart : function() {
  348. if(!module.others.active() && !keepDimmed) {
  349. module.hideDimmer();
  350. }
  351. module.remove.keyboardShortcuts();
  352. },
  353. onComplete : function() {
  354. settings.onHidden.call(element);
  355. module.restore.focus();
  356. callback();
  357. }
  358. })
  359. ;
  360. }
  361. else {
  362. module.error(error.noTransition);
  363. }
  364. }
  365. },
  366. showDimmer: function() {
  367. if($dimmable.dimmer('is animating') || !$dimmable.dimmer('is active') ) {
  368. module.debug('Showing dimmer');
  369. $dimmable.dimmer('show');
  370. }
  371. else {
  372. module.debug('Dimmer already visible');
  373. }
  374. },
  375. hideDimmer: function() {
  376. if( $dimmable.dimmer('is animating') || ($dimmable.dimmer('is active')) ) {
  377. $dimmable.dimmer('hide', function() {
  378. module.remove.clickaway();
  379. module.remove.screenHeight();
  380. });
  381. }
  382. else {
  383. module.debug('Dimmer is not visible cannot hide');
  384. return;
  385. }
  386. },
  387. hideAll: function(callback) {
  388. var
  389. $visibleModals = $allModals.filter('.' + className.active + ', .' + className.animating)
  390. ;
  391. callback = $.isFunction(callback)
  392. ? callback
  393. : function(){}
  394. ;
  395. if( $visibleModals.length > 0 ) {
  396. module.debug('Hiding all visible modals');
  397. module.hideDimmer();
  398. $visibleModals
  399. .modal('hide modal', callback)
  400. ;
  401. }
  402. },
  403. hideOthers: function(callback) {
  404. var
  405. $visibleModals = $otherModals.filter('.' + className.active + ', .' + className.animating)
  406. ;
  407. callback = $.isFunction(callback)
  408. ? callback
  409. : function(){}
  410. ;
  411. if( $visibleModals.length > 0 ) {
  412. module.debug('Hiding other modals', $otherModals);
  413. $visibleModals
  414. .modal('hide modal', callback, true)
  415. ;
  416. }
  417. },
  418. others: {
  419. active: function() {
  420. return ($otherModals.filter('.' + className.active).length > 0);
  421. },
  422. animating: function() {
  423. return ($otherModals.filter('.' + className.animating).length > 0);
  424. }
  425. },
  426. add: {
  427. keyboardShortcuts: function() {
  428. module.verbose('Adding keyboard shortcuts');
  429. $document
  430. .on('keyup' + eventNamespace, module.event.keyboard)
  431. ;
  432. }
  433. },
  434. save: {
  435. focus: function() {
  436. $focusedElement = $(document.activeElement).blur();
  437. }
  438. },
  439. restore: {
  440. focus: function() {
  441. if($focusedElement && $focusedElement.length > 0) {
  442. $focusedElement.focus();
  443. }
  444. }
  445. },
  446. remove: {
  447. active: function() {
  448. $module.removeClass(className.active);
  449. },
  450. clickaway: function() {
  451. if(settings.closable) {
  452. $dimmer
  453. .off('click' + elementNamespace)
  454. ;
  455. }
  456. },
  457. bodyStyle: function() {
  458. if($body.attr('style') === '') {
  459. module.verbose('Removing style attribute');
  460. $body.removeAttr('style');
  461. }
  462. },
  463. screenHeight: function() {
  464. module.debug('Removing page height');
  465. $body
  466. .css('height', '')
  467. ;
  468. },
  469. keyboardShortcuts: function() {
  470. module.verbose('Removing keyboard shortcuts');
  471. $document
  472. .off('keyup' + eventNamespace)
  473. ;
  474. },
  475. scrolling: function() {
  476. $dimmable.removeClass(className.scrolling);
  477. $module.removeClass(className.scrolling);
  478. }
  479. },
  480. cacheSizes: function() {
  481. var
  482. modalHeight = $module.outerHeight()
  483. ;
  484. if(module.cache === undefined || modalHeight !== 0) {
  485. module.cache = {
  486. pageHeight : $(document).outerHeight(),
  487. height : modalHeight + settings.offset,
  488. contextHeight : (settings.context == 'body')
  489. ? $(window).height()
  490. : $dimmable.height()
  491. };
  492. }
  493. module.debug('Caching modal and container sizes', module.cache);
  494. },
  495. can: {
  496. fit: function() {
  497. return ( ( module.cache.height + (settings.padding * 2) ) < module.cache.contextHeight);
  498. }
  499. },
  500. is: {
  501. active: function() {
  502. return $module.hasClass(className.active);
  503. },
  504. animating: function() {
  505. return $module.transition('is supported')
  506. ? $module.transition('is animating')
  507. : $module.is(':visible')
  508. ;
  509. },
  510. scrolling: function() {
  511. return $dimmable.hasClass(className.scrolling);
  512. },
  513. modernBrowser: function() {
  514. // appName for IE11 reports 'Netscape' can no longer use
  515. return !(window.ActiveXObject || "ActiveXObject" in window);
  516. }
  517. },
  518. set: {
  519. autofocus: function() {
  520. var
  521. $inputs = $module.find(':input').filter(':visible'),
  522. $autofocus = $inputs.filter('[autofocus]'),
  523. $input = ($autofocus.length > 0)
  524. ? $autofocus.first()
  525. : $inputs.first()
  526. ;
  527. if($input.length > 0) {
  528. $input.focus();
  529. }
  530. },
  531. clickaway: function() {
  532. if(settings.closable) {
  533. $dimmer
  534. .on('click' + elementNamespace, module.event.click)
  535. ;
  536. }
  537. },
  538. screenHeight: function() {
  539. if( module.can.fit() ) {
  540. $body.css('height', '');
  541. }
  542. else {
  543. module.debug('Modal is taller than page content, resizing page height');
  544. $body
  545. .css('height', module.cache.height + (settings.padding * 2) )
  546. ;
  547. }
  548. },
  549. active: function() {
  550. $module.addClass(className.active);
  551. },
  552. scrolling: function() {
  553. $dimmable.addClass(className.scrolling);
  554. $module.addClass(className.scrolling);
  555. },
  556. type: function() {
  557. if(module.can.fit()) {
  558. module.verbose('Modal fits on screen');
  559. if(!module.others.active() && !module.others.animating()) {
  560. module.remove.scrolling();
  561. }
  562. }
  563. else {
  564. module.verbose('Modal cannot fit on screen setting to scrolling');
  565. module.set.scrolling();
  566. }
  567. },
  568. position: function() {
  569. module.verbose('Centering modal on page', module.cache);
  570. if(module.can.fit()) {
  571. $module
  572. .css({
  573. top: '',
  574. marginTop: -(module.cache.height / 2)
  575. })
  576. ;
  577. }
  578. else {
  579. $module
  580. .css({
  581. marginTop : '',
  582. top : $document.scrollTop()
  583. })
  584. ;
  585. }
  586. },
  587. undetached: function() {
  588. $dimmable.addClass(className.undetached);
  589. }
  590. },
  591. setting: function(name, value) {
  592. module.debug('Changing setting', name, value);
  593. if( $.isPlainObject(name) ) {
  594. $.extend(true, settings, name);
  595. }
  596. else if(value !== undefined) {
  597. settings[name] = value;
  598. }
  599. else {
  600. return settings[name];
  601. }
  602. },
  603. internal: function(name, value) {
  604. if( $.isPlainObject(name) ) {
  605. $.extend(true, module, name);
  606. }
  607. else if(value !== undefined) {
  608. module[name] = value;
  609. }
  610. else {
  611. return module[name];
  612. }
  613. },
  614. debug: function() {
  615. if(settings.debug) {
  616. if(settings.performance) {
  617. module.performance.log(arguments);
  618. }
  619. else {
  620. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  621. module.debug.apply(console, arguments);
  622. }
  623. }
  624. },
  625. verbose: function() {
  626. if(settings.verbose && settings.debug) {
  627. if(settings.performance) {
  628. module.performance.log(arguments);
  629. }
  630. else {
  631. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  632. module.verbose.apply(console, arguments);
  633. }
  634. }
  635. },
  636. error: function() {
  637. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  638. module.error.apply(console, arguments);
  639. },
  640. performance: {
  641. log: function(message) {
  642. var
  643. currentTime,
  644. executionTime,
  645. previousTime
  646. ;
  647. if(settings.performance) {
  648. currentTime = new Date().getTime();
  649. previousTime = time || currentTime;
  650. executionTime = currentTime - previousTime;
  651. time = currentTime;
  652. performance.push({
  653. 'Name' : message[0],
  654. 'Arguments' : [].slice.call(message, 1) || '',
  655. 'Element' : element,
  656. 'Execution Time' : executionTime
  657. });
  658. }
  659. clearTimeout(module.performance.timer);
  660. module.performance.timer = setTimeout(module.performance.display, 500);
  661. },
  662. display: function() {
  663. var
  664. title = settings.name + ':',
  665. totalTime = 0
  666. ;
  667. time = false;
  668. clearTimeout(module.performance.timer);
  669. $.each(performance, function(index, data) {
  670. totalTime += data['Execution Time'];
  671. });
  672. title += ' ' + totalTime + 'ms';
  673. if(moduleSelector) {
  674. title += ' \'' + moduleSelector + '\'';
  675. }
  676. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  677. console.groupCollapsed(title);
  678. if(console.table) {
  679. console.table(performance);
  680. }
  681. else {
  682. $.each(performance, function(index, data) {
  683. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  684. });
  685. }
  686. console.groupEnd();
  687. }
  688. performance = [];
  689. }
  690. },
  691. invoke: function(query, passedArguments, context) {
  692. var
  693. object = instance,
  694. maxDepth,
  695. found,
  696. response
  697. ;
  698. passedArguments = passedArguments || queryArguments;
  699. context = element || context;
  700. if(typeof query == 'string' && object !== undefined) {
  701. query = query.split(/[\. ]/);
  702. maxDepth = query.length - 1;
  703. $.each(query, function(depth, value) {
  704. var camelCaseValue = (depth != maxDepth)
  705. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  706. : query
  707. ;
  708. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  709. object = object[camelCaseValue];
  710. }
  711. else if( object[camelCaseValue] !== undefined ) {
  712. found = object[camelCaseValue];
  713. return false;
  714. }
  715. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  716. object = object[value];
  717. }
  718. else if( object[value] !== undefined ) {
  719. found = object[value];
  720. return false;
  721. }
  722. else {
  723. return false;
  724. }
  725. });
  726. }
  727. if ( $.isFunction( found ) ) {
  728. response = found.apply(context, passedArguments);
  729. }
  730. else if(found !== undefined) {
  731. response = found;
  732. }
  733. if($.isArray(returnedValue)) {
  734. returnedValue.push(response);
  735. }
  736. else if(returnedValue !== undefined) {
  737. returnedValue = [returnedValue, response];
  738. }
  739. else if(response !== undefined) {
  740. returnedValue = response;
  741. }
  742. return found;
  743. }
  744. };
  745. if(methodInvoked) {
  746. if(instance === undefined) {
  747. module.initialize();
  748. }
  749. module.invoke(query);
  750. }
  751. else {
  752. if(instance !== undefined) {
  753. instance.invoke('destroy');
  754. }
  755. module.initialize();
  756. }
  757. })
  758. ;
  759. return (returnedValue !== undefined)
  760. ? returnedValue
  761. : this
  762. ;
  763. };
  764. $.fn.modal.settings = {
  765. name : 'Modal',
  766. namespace : 'modal',
  767. debug : false,
  768. verbose : false,
  769. performance : true,
  770. observeChanges : false,
  771. allowMultiple : false,
  772. detachable : true,
  773. closable : true,
  774. autofocus : true,
  775. inverted : false,
  776. blurring : false,
  777. dimmerSettings : {
  778. closable : false,
  779. useCSS : true
  780. },
  781. context : 'body',
  782. queue : false,
  783. duration : 500,
  784. offset : 0,
  785. transition : 'scale',
  786. // padding with edge of page
  787. padding : 50,
  788. // called before show animation
  789. onShow : function(){},
  790. // called after show animation
  791. onVisible : function(){},
  792. // called before hide animation
  793. onHide : function(){ return true; },
  794. // called after hide animation
  795. onHidden : function(){},
  796. // called after approve selector match
  797. onApprove : function(){ return true; },
  798. // called after deny selector match
  799. onDeny : function(){ return true; },
  800. selector : {
  801. close : '> .close',
  802. approve : '.actions .positive, .actions .approve, .actions .ok',
  803. deny : '.actions .negative, .actions .deny, .actions .cancel',
  804. modal : '.ui.modal'
  805. },
  806. error : {
  807. dimmer : 'UI Dimmer, a required component is not included in this page',
  808. method : 'The method you called is not defined.',
  809. notFound : 'The element you specified could not be found'
  810. },
  811. className : {
  812. active : 'active',
  813. animating : 'animating',
  814. blurring : 'blurring',
  815. scrolling : 'scrolling',
  816. undetached : 'undetached'
  817. }
  818. };
  819. })( jQuery, window, document );