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.

753 lines
22 KiB

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