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.

790 lines
23 KiB

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