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.

835 lines
25 KiB

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