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.

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