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.

683 lines
20 KiB

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