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.

751 lines
22 KiB

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