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
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. 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. onStart : function() {
  236. module.cacheSizes();
  237. module.set.position();
  238. module.set.screenHeight();
  239. module.set.type();
  240. module.set.clickaway();
  241. },
  242. onComplete : 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. onStart : function() {
  319. module.remove.keyboardShortcuts();
  320. },
  321. onComplete : 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 );