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.

774 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
10 years ago
11 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() || module.is.animating() ) {
  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({
  233. animation : settings.transition + ' in',
  234. duration : settings.duration,
  235. complete : function() {
  236. $.proxy(settings.onVisible, element)();
  237. module.set.active();
  238. callback();
  239. }
  240. })
  241. ;
  242. }
  243. else {
  244. module.debug('Showing modal with javascript');
  245. $module
  246. .fadeIn(settings.duration, settings.easing, function() {
  247. $.proxy(settings.onVisible, element)();
  248. module.set.active();
  249. callback();
  250. })
  251. ;
  252. }
  253. }
  254. }
  255. else {
  256. module.debug('Modal is already visible');
  257. }
  258. },
  259. showDimmer: function() {
  260. if( !$dimmable.dimmer('is active') ) {
  261. module.debug('Showing dimmer');
  262. $dimmable.dimmer('show');
  263. }
  264. else {
  265. module.debug('Dimmer already visible');
  266. }
  267. },
  268. hide: function(callback) {
  269. callback = $.isFunction(callback)
  270. ? callback
  271. : function(){}
  272. ;
  273. if($allModals.filter(':visible').size() <= 1) {
  274. module.hideDimmer();
  275. }
  276. module.hideModal(callback);
  277. },
  278. hideDimmer: function() {
  279. if( !($dimmable.dimmer('is active') || $dimmable.dimmer('is animating')) ) {
  280. module.debug('Dimmer is not visible cannot hide');
  281. return;
  282. }
  283. module.debug('Hiding dimmer');
  284. if(settings.closable) {
  285. $dimmer
  286. .off('click' + eventNamespace)
  287. ;
  288. }
  289. $dimmable.dimmer('hide', function() {
  290. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  291. module.remove.screenHeight();
  292. }
  293. module.remove.active();
  294. });
  295. },
  296. hideModal: function(callback) {
  297. callback = $.isFunction(callback)
  298. ? callback
  299. : function(){}
  300. ;
  301. if( !(module.is.active() || module.is.animating()) ) {
  302. module.debug('Cannot hide modal it is not active');
  303. return;
  304. }
  305. module.debug('Hiding modal');
  306. module.remove.keyboardShortcuts();
  307. $.proxy(settings.onHide, element)();
  308. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  309. $module
  310. .transition({
  311. animation : settings.transition + ' out',
  312. duration : settings.duration,
  313. complete : function() {
  314. $.proxy(settings.onHidden, element)();
  315. module.remove.active();
  316. module.restore.focus();
  317. callback();
  318. }
  319. })
  320. ;
  321. }
  322. else {
  323. $module
  324. .fadeOut(settings.duration, settings.easing, function() {
  325. $.proxy(settings.onHidden, element)();
  326. module.remove.active();
  327. module.restore.focus();
  328. callback();
  329. })
  330. ;
  331. }
  332. },
  333. hideAll: function(callback) {
  334. callback = $.isFunction(callback)
  335. ? callback
  336. : function(){}
  337. ;
  338. if( $allModals.is(':visible') ) {
  339. module.debug('Hiding all visible modals');
  340. module.hideDimmer();
  341. $allModals
  342. .filter(':visible')
  343. .modal('hide modal', callback)
  344. ;
  345. }
  346. },
  347. hideOthers: function(callback) {
  348. callback = $.isFunction(callback)
  349. ? callback
  350. : function(){}
  351. ;
  352. if( $otherModals.is(':visible') ) {
  353. module.debug('Hiding other modals');
  354. $otherModals
  355. .filter(':visible')
  356. .modal('hide modal', callback)
  357. ;
  358. }
  359. },
  360. add: {
  361. keyboardShortcuts: function() {
  362. module.verbose('Adding keyboard shortcuts');
  363. $document
  364. .on('keyup' + eventNamespace, module.event.keyboard)
  365. ;
  366. }
  367. },
  368. save: {
  369. focus: function() {
  370. $focusedElement = $(document.activeElement).blur();
  371. }
  372. },
  373. restore: {
  374. focus: function() {
  375. if($focusedElement && $focusedElement.size() > 0) {
  376. $focusedElement.focus();
  377. }
  378. }
  379. },
  380. remove: {
  381. active: function() {
  382. $module.removeClass(className.active);
  383. },
  384. screenHeight: function() {
  385. if(module.cache.height > module.cache.pageHeight) {
  386. module.debug('Removing page height');
  387. $body
  388. .css('height', '')
  389. ;
  390. }
  391. },
  392. keyboardShortcuts: function() {
  393. module.verbose('Removing keyboard shortcuts');
  394. $document
  395. .off('keyup' + eventNamespace)
  396. ;
  397. },
  398. scrolling: function() {
  399. $dimmable.removeClass(className.scrolling);
  400. $module.removeClass(className.scrolling);
  401. }
  402. },
  403. cacheSizes: function() {
  404. var
  405. modalHeight = $module.outerHeight()
  406. ;
  407. if(modalHeight !== 0) {
  408. module.cache = {
  409. pageHeight : $(document).outerHeight(),
  410. height : modalHeight + settings.offset,
  411. contextHeight : (settings.context == 'body')
  412. ? $(window).height()
  413. : $dimmable.height()
  414. };
  415. }
  416. module.debug('Caching modal and container sizes', module.cache);
  417. },
  418. can: {
  419. fit: function() {
  420. return (module.cache.height < module.cache.contextHeight);
  421. }
  422. },
  423. is: {
  424. active: function() {
  425. return $module.hasClass(className.active);
  426. },
  427. animating: function() {
  428. return $module.transition('is supported')
  429. ? $module.transition('is animating')
  430. : $module.is(':visible')
  431. ;
  432. },
  433. modernBrowser: function() {
  434. // appName for IE11 reports 'Netscape' can no longer use
  435. return !(window.ActiveXObject || "ActiveXObject" in window);
  436. }
  437. },
  438. set: {
  439. screenHeight: function() {
  440. if(module.cache.height > module.cache.pageHeight) {
  441. module.debug('Modal is taller than page content, resizing page height');
  442. $body
  443. .css('height', module.cache.height + settings.padding)
  444. ;
  445. }
  446. },
  447. active: function() {
  448. module.add.keyboardShortcuts();
  449. module.save.focus();
  450. $module
  451. .addClass(className.active)
  452. ;
  453. if(settings.closable) {
  454. $dimmer
  455. .off('click' + eventNamespace)
  456. .on('click' + eventNamespace, module.event.click)
  457. ;
  458. }
  459. if(settings.autofocus) {
  460. var
  461. $inputs = $module.find(':input:visible'),
  462. $autofocus = $inputs.filter('[autofocus]'),
  463. $input = ($autofocus.size() > 0)
  464. ? $autofocus
  465. : $inputs
  466. ;
  467. $input.first().focus();
  468. }
  469. },
  470. scrolling: function() {
  471. $dimmable.addClass(className.scrolling);
  472. $module.addClass(className.scrolling);
  473. },
  474. type: function() {
  475. if(module.can.fit()) {
  476. module.verbose('Modal fits on screen');
  477. module.remove.scrolling();
  478. }
  479. else {
  480. module.verbose('Modal cannot fit on screen setting to scrolling');
  481. module.set.scrolling();
  482. }
  483. },
  484. position: function() {
  485. module.verbose('Centering modal on page', module.cache);
  486. if(module.can.fit()) {
  487. $module
  488. .css({
  489. top: '',
  490. marginTop: -(module.cache.height / 2)
  491. })
  492. ;
  493. }
  494. else {
  495. $module
  496. .css({
  497. marginTop : '',
  498. top : $document.scrollTop()
  499. })
  500. ;
  501. }
  502. }
  503. },
  504. setting: function(name, value) {
  505. module.debug('Changing setting', name, value);
  506. if( $.isPlainObject(name) ) {
  507. $.extend(true, settings, name);
  508. }
  509. else if(value !== undefined) {
  510. settings[name] = value;
  511. }
  512. else {
  513. return settings[name];
  514. }
  515. },
  516. internal: function(name, value) {
  517. if( $.isPlainObject(name) ) {
  518. $.extend(true, module, name);
  519. }
  520. else if(value !== undefined) {
  521. module[name] = value;
  522. }
  523. else {
  524. return module[name];
  525. }
  526. },
  527. debug: function() {
  528. if(settings.debug) {
  529. if(settings.performance) {
  530. module.performance.log(arguments);
  531. }
  532. else {
  533. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  534. module.debug.apply(console, arguments);
  535. }
  536. }
  537. },
  538. verbose: function() {
  539. if(settings.verbose && settings.debug) {
  540. if(settings.performance) {
  541. module.performance.log(arguments);
  542. }
  543. else {
  544. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  545. module.verbose.apply(console, arguments);
  546. }
  547. }
  548. },
  549. error: function() {
  550. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  551. module.error.apply(console, arguments);
  552. },
  553. performance: {
  554. log: function(message) {
  555. var
  556. currentTime,
  557. executionTime,
  558. previousTime
  559. ;
  560. if(settings.performance) {
  561. currentTime = new Date().getTime();
  562. previousTime = time || currentTime;
  563. executionTime = currentTime - previousTime;
  564. time = currentTime;
  565. performance.push({
  566. 'Name' : message[0],
  567. 'Arguments' : [].slice.call(message, 1) || '',
  568. 'Element' : element,
  569. 'Execution Time' : executionTime
  570. });
  571. }
  572. clearTimeout(module.performance.timer);
  573. module.performance.timer = setTimeout(module.performance.display, 100);
  574. },
  575. display: function() {
  576. var
  577. title = settings.name + ':',
  578. totalTime = 0
  579. ;
  580. time = false;
  581. clearTimeout(module.performance.timer);
  582. $.each(performance, function(index, data) {
  583. totalTime += data['Execution Time'];
  584. });
  585. title += ' ' + totalTime + 'ms';
  586. if(moduleSelector) {
  587. title += ' \'' + moduleSelector + '\'';
  588. }
  589. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  590. console.groupCollapsed(title);
  591. if(console.table) {
  592. console.table(performance);
  593. }
  594. else {
  595. $.each(performance, function(index, data) {
  596. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  597. });
  598. }
  599. console.groupEnd();
  600. }
  601. performance = [];
  602. }
  603. },
  604. invoke: function(query, passedArguments, context) {
  605. var
  606. object = instance,
  607. maxDepth,
  608. found,
  609. response
  610. ;
  611. passedArguments = passedArguments || queryArguments;
  612. context = element || context;
  613. if(typeof query == 'string' && object !== undefined) {
  614. query = query.split(/[\. ]/);
  615. maxDepth = query.length - 1;
  616. $.each(query, function(depth, value) {
  617. var camelCaseValue = (depth != maxDepth)
  618. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  619. : query
  620. ;
  621. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  622. object = object[camelCaseValue];
  623. }
  624. else if( object[camelCaseValue] !== undefined ) {
  625. found = object[camelCaseValue];
  626. return false;
  627. }
  628. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  629. object = object[value];
  630. }
  631. else if( object[value] !== undefined ) {
  632. found = object[value];
  633. return false;
  634. }
  635. else {
  636. return false;
  637. }
  638. });
  639. }
  640. if ( $.isFunction( found ) ) {
  641. response = found.apply(context, passedArguments);
  642. }
  643. else if(found !== undefined) {
  644. response = found;
  645. }
  646. if($.isArray(returnedValue)) {
  647. returnedValue.push(response);
  648. }
  649. else if(returnedValue !== undefined) {
  650. returnedValue = [returnedValue, response];
  651. }
  652. else if(response !== undefined) {
  653. returnedValue = response;
  654. }
  655. return found;
  656. }
  657. };
  658. if(methodInvoked) {
  659. if(instance === undefined) {
  660. module.initialize();
  661. }
  662. module.invoke(query);
  663. }
  664. else {
  665. if(instance !== undefined) {
  666. module.destroy();
  667. }
  668. module.initialize();
  669. }
  670. })
  671. ;
  672. return (returnedValue !== undefined)
  673. ? returnedValue
  674. : this
  675. ;
  676. };
  677. $.fn.modal.settings = {
  678. name : 'Modal',
  679. namespace : 'modal',
  680. debug : false,
  681. verbose : true,
  682. performance : true,
  683. allowMultiple : false,
  684. detachable : true,
  685. closable : true,
  686. autofocus : true,
  687. context : 'body',
  688. duration : 500,
  689. easing : 'easeOutExpo',
  690. offset : 0,
  691. transition : 'scale',
  692. padding : 30,
  693. onShow : function(){},
  694. onHide : function(){},
  695. onVisible : function(){},
  696. onHidden : function(){},
  697. onApprove : function(){ return true; },
  698. onDeny : function(){ return true; },
  699. selector : {
  700. close : '.close, .actions .button',
  701. approve : '.actions .positive, .actions .approve, .actions .ok',
  702. deny : '.actions .negative, .actions .deny, .actions .cancel',
  703. modal : '.ui.modal'
  704. },
  705. error : {
  706. dimmer : 'UI Dimmer, a required component is not included in this page',
  707. method : 'The method you called is not defined.',
  708. notFound : 'The element you specified could not be found'
  709. },
  710. className : {
  711. active : 'active',
  712. scrolling : 'scrolling'
  713. }
  714. };
  715. })( jQuery, window , document );