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.

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