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.

765 lines
22 KiB

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() ) {
  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. if(settings.autofocus) {
  449. var
  450. $inputs = $module.find(':input:visible'),
  451. $autofocus = $inputs.filter('[autofocus]'),
  452. $input = ($autofocus.size() > 0)
  453. ? $autofocus
  454. : $inputs
  455. ;
  456. $input.first().focus();
  457. }
  458. },
  459. scrolling: function() {
  460. $dimmable.addClass(className.scrolling);
  461. $module.addClass(className.scrolling);
  462. },
  463. type: function() {
  464. if(module.can.fit()) {
  465. module.verbose('Modal fits on screen');
  466. module.remove.scrolling();
  467. }
  468. else {
  469. module.verbose('Modal cannot fit on screen setting to scrolling');
  470. module.set.scrolling();
  471. }
  472. },
  473. position: function() {
  474. module.verbose('Centering modal on page', module.cache);
  475. if(module.can.fit()) {
  476. $module
  477. .css({
  478. top: '',
  479. marginTop: -(module.cache.height / 2)
  480. })
  481. ;
  482. }
  483. else {
  484. $module
  485. .css({
  486. marginTop : '',
  487. top : $document.scrollTop()
  488. })
  489. ;
  490. }
  491. }
  492. },
  493. setting: function(name, value) {
  494. module.debug('Changing setting', name, value);
  495. if( $.isPlainObject(name) ) {
  496. $.extend(true, settings, name);
  497. }
  498. else if(value !== undefined) {
  499. settings[name] = value;
  500. }
  501. else {
  502. return settings[name];
  503. }
  504. },
  505. internal: function(name, value) {
  506. if( $.isPlainObject(name) ) {
  507. $.extend(true, module, name);
  508. }
  509. else if(value !== undefined) {
  510. module[name] = value;
  511. }
  512. else {
  513. return module[name];
  514. }
  515. },
  516. debug: function() {
  517. if(settings.debug) {
  518. if(settings.performance) {
  519. module.performance.log(arguments);
  520. }
  521. else {
  522. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  523. module.debug.apply(console, arguments);
  524. }
  525. }
  526. },
  527. verbose: function() {
  528. if(settings.verbose && settings.debug) {
  529. if(settings.performance) {
  530. module.performance.log(arguments);
  531. }
  532. else {
  533. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  534. module.verbose.apply(console, arguments);
  535. }
  536. }
  537. },
  538. error: function() {
  539. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  540. module.error.apply(console, arguments);
  541. },
  542. performance: {
  543. log: function(message) {
  544. var
  545. currentTime,
  546. executionTime,
  547. previousTime
  548. ;
  549. if(settings.performance) {
  550. currentTime = new Date().getTime();
  551. previousTime = time || currentTime;
  552. executionTime = currentTime - previousTime;
  553. time = currentTime;
  554. performance.push({
  555. 'Element' : element,
  556. 'Name' : message[0],
  557. 'Arguments' : [].slice.call(message, 1) || '',
  558. 'Execution Time' : executionTime
  559. });
  560. }
  561. clearTimeout(module.performance.timer);
  562. module.performance.timer = setTimeout(module.performance.display, 100);
  563. },
  564. display: function() {
  565. var
  566. title = settings.name + ':',
  567. totalTime = 0
  568. ;
  569. time = false;
  570. clearTimeout(module.performance.timer);
  571. $.each(performance, function(index, data) {
  572. totalTime += data['Execution Time'];
  573. });
  574. title += ' ' + totalTime + 'ms';
  575. if(moduleSelector) {
  576. title += ' \'' + moduleSelector + '\'';
  577. }
  578. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  579. console.groupCollapsed(title);
  580. if(console.table) {
  581. console.table(performance);
  582. }
  583. else {
  584. $.each(performance, function(index, data) {
  585. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  586. });
  587. }
  588. console.groupEnd();
  589. }
  590. performance = [];
  591. }
  592. },
  593. invoke: function(query, passedArguments, context) {
  594. var
  595. object = instance,
  596. maxDepth,
  597. found,
  598. response
  599. ;
  600. passedArguments = passedArguments || queryArguments;
  601. context = element || context;
  602. if(typeof query == 'string' && object !== undefined) {
  603. query = query.split(/[\. ]/);
  604. maxDepth = query.length - 1;
  605. $.each(query, function(depth, value) {
  606. var camelCaseValue = (depth != maxDepth)
  607. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  608. : query
  609. ;
  610. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  611. object = object[camelCaseValue];
  612. }
  613. else if( object[camelCaseValue] !== undefined ) {
  614. found = object[camelCaseValue];
  615. return false;
  616. }
  617. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  618. object = object[value];
  619. }
  620. else if( object[value] !== undefined ) {
  621. found = object[value];
  622. return false;
  623. }
  624. else {
  625. return false;
  626. }
  627. });
  628. }
  629. if ( $.isFunction( found ) ) {
  630. response = found.apply(context, passedArguments);
  631. }
  632. else if(found !== undefined) {
  633. response = found;
  634. }
  635. if($.isArray(returnedValue)) {
  636. returnedValue.push(response);
  637. }
  638. else if(returnedValue !== undefined) {
  639. returnedValue = [returnedValue, response];
  640. }
  641. else if(response !== undefined) {
  642. returnedValue = response;
  643. }
  644. return found;
  645. }
  646. };
  647. if(methodInvoked) {
  648. if(instance === undefined) {
  649. module.initialize();
  650. }
  651. module.invoke(query);
  652. }
  653. else {
  654. if(instance !== undefined) {
  655. module.destroy();
  656. }
  657. module.initialize();
  658. }
  659. })
  660. ;
  661. return (returnedValue !== undefined)
  662. ? returnedValue
  663. : this
  664. ;
  665. };
  666. $.fn.modal.settings = {
  667. name : 'Modal',
  668. namespace : 'modal',
  669. debug : false,
  670. verbose : true,
  671. performance : true,
  672. allowMultiple : false,
  673. detachable : true,
  674. closable : true,
  675. autofocus : true,
  676. context : 'body',
  677. duration : 500,
  678. easing : 'easeOutExpo',
  679. offset : 0,
  680. transition : 'scale',
  681. padding : 30,
  682. onShow : function(){},
  683. onHide : function(){},
  684. onVisible : function(){},
  685. onHidden : function(){},
  686. onApprove : function(){ return true; },
  687. onDeny : function(){ return true; },
  688. selector : {
  689. close : '.close, .actions .button',
  690. approve : '.actions .positive, .actions .approve, .actions .ok',
  691. deny : '.actions .negative, .actions .deny, .actions .cancel',
  692. modal : '.ui.modal'
  693. },
  694. error : {
  695. dimmer : 'UI Dimmer, a required component is not included in this page',
  696. method : 'The method you called is not defined.',
  697. notFound : 'The element you specified could not be found'
  698. },
  699. className : {
  700. active : 'active',
  701. scrolling : 'scrolling'
  702. }
  703. };
  704. })( jQuery, window , document );