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.

745 lines
22 KiB

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