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.

854 lines
25 KiB

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