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.

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