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.

563 lines
16 KiB

  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. invokedResponse
  23. ;
  24. $allModules
  25. .each(function() {
  26. var
  27. settings = ( $.isPlainObject(parameters) )
  28. ? $.extend(true, {}, $.fn.modal.settings, parameters)
  29. : $.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. $otherModals = $allModules.not($module),
  40. $close = $module.find(selector.close),
  41. $focusedElement,
  42. $dimmer,
  43. element = this,
  44. instance = $module.data(moduleNamespace),
  45. module
  46. ;
  47. module = {
  48. initialize: function() {
  49. module.verbose('Initializing dimmer', $context);
  50. $dimmer = $context
  51. .dimmer('add content', $module)
  52. ;
  53. module.verbose('Attaching close events', $close);
  54. $close
  55. .on('click' + eventNamespace, module.event.close)
  56. ;
  57. $window
  58. .on('resize', function() {
  59. module.event.debounce(module.refresh, 50);
  60. })
  61. ;
  62. module.instantiate();
  63. },
  64. instantiate: function() {
  65. module.verbose('Storing instance of modal');
  66. instance = module;
  67. $module
  68. .data(moduleNamespace, instance)
  69. ;
  70. },
  71. destroy: function() {
  72. module.verbose('Destroying previous modal');
  73. $module
  74. .off(eventNamespace)
  75. ;
  76. },
  77. refresh: function() {
  78. module.remove.scrolling();
  79. module.cacheSizes();
  80. module.set.type();
  81. module.set.position();
  82. },
  83. attachEvents: function(selector, event) {
  84. var
  85. $toggle = $(selector)
  86. ;
  87. event = $.isFunction(module[event])
  88. ? module[event]
  89. : module.toggle
  90. ;
  91. if($toggle.size() > 0) {
  92. module.debug('Attaching modal events to element', selector, event);
  93. $toggle
  94. .off(eventNamespace)
  95. .on('click' + eventNamespace, event)
  96. ;
  97. }
  98. else {
  99. module.error(error.notFound);
  100. }
  101. },
  102. event: {
  103. close: function() {
  104. module.verbose('Close button pressed');
  105. module.hide();
  106. },
  107. debounce: function(method, delay) {
  108. clearTimeout(module.timer);
  109. module.timer = setTimeout(method, delay);
  110. },
  111. keyboard: function(event) {
  112. var
  113. keyCode = event.which,
  114. escapeKey = 27
  115. ;
  116. if(keyCode == escapeKey) {
  117. module.debug('Escape key pressed hiding modal');
  118. module.hide();
  119. event.preventDefault();
  120. }
  121. },
  122. resize: function() {
  123. if( $dimmer.dimmer('is active') ) {
  124. module.refresh();
  125. }
  126. }
  127. },
  128. toggle: function() {
  129. if( module.is.active() ) {
  130. module.hide();
  131. }
  132. else {
  133. module.show();
  134. }
  135. },
  136. show: function() {
  137. module.showDimmer();
  138. module.cacheSizes();
  139. module.set.position();
  140. module.hideAll();
  141. if(settings.transition && $.fn.transition !== undefined) {
  142. $module
  143. .transition(settings.transition + ' in', settings.duration, module.set.active)
  144. ;
  145. }
  146. else {
  147. $module
  148. .fadeIn(settings.duration, settings.easing, module.set.active)
  149. ;
  150. }
  151. module.debug('Triggering dimmer');
  152. $.proxy(settings.onShow, element)();
  153. },
  154. showDimmer: function() {
  155. module.debug('Showing modal');
  156. module.set.dimmerSettings();
  157. $dimmer.dimmer('show');
  158. },
  159. hide: function() {
  160. if( $dimmer.dimmer('is active') ) {
  161. $dimmer.dimmer('hide');
  162. }
  163. if( module.is.active() ) {
  164. module.hideModal();
  165. $.proxy(settings.onHide, element)();
  166. }
  167. else {
  168. module.debug('Cannot hide modal, modal is not visible');
  169. }
  170. },
  171. hideDimmer: function() {
  172. module.debug('Hiding dimmer');
  173. $dimmer.dimmer('hide');
  174. },
  175. hideModal: function() {
  176. module.debug('Hiding modal');
  177. module.remove.keyboardShortcuts();
  178. if(settings.transition && $.fn.transition !== undefined) {
  179. $module
  180. .transition(settings.transition + ' out', settings.duration, function() {
  181. module.remove.active();
  182. module.restore.focus();
  183. })
  184. ;
  185. }
  186. else {
  187. $module
  188. .fadeOut(settings.duration, settings.easing, function() {
  189. module.remove.active();
  190. module.restore.focus();
  191. })
  192. ;
  193. }
  194. },
  195. hideAll: function() {
  196. $otherModals
  197. .filter(':visible')
  198. .modal('hide')
  199. ;
  200. },
  201. add: {
  202. keyboardShortcuts: function() {
  203. module.verbose('Adding keyboard shortcuts');
  204. $document
  205. .on('keyup' + eventNamespace, module.event.keyboard)
  206. ;
  207. }
  208. },
  209. save: {
  210. focus: function() {
  211. $focusedElement = $(document.activeElement).blur();
  212. }
  213. },
  214. restore: {
  215. focus: function() {
  216. if($focusedElement.size() > 0) {
  217. $focusedElement.focus();
  218. }
  219. }
  220. },
  221. remove: {
  222. active: function() {
  223. $module.removeClass(className.active);
  224. },
  225. keyboardShortcuts: function() {
  226. module.verbose('Removing keyboard shortcuts');
  227. $document
  228. .off('keyup' + eventNamespace)
  229. ;
  230. },
  231. scrolling: function() {
  232. $dimmer.removeClass(className.scrolling);
  233. $module.removeClass(className.scrolling);
  234. }
  235. },
  236. cacheSizes: function() {
  237. module.cache = {
  238. height : $module.outerHeight() + settings.offset,
  239. contextHeight : (settings.context == 'body')
  240. ? $(window).height()
  241. : $dimmer.height()
  242. };
  243. module.debug('Caching modal and container sizes', module.cache);
  244. },
  245. can: {
  246. fit: function() {
  247. return (module.cache.height < module.cache.contextHeight);
  248. }
  249. },
  250. is: {
  251. active: function() {
  252. return $module.hasClass(className.active);
  253. }
  254. },
  255. set: {
  256. active: function() {
  257. module.save.focus();
  258. module.set.type();
  259. $module
  260. .addClass(className.active)
  261. ;
  262. if(settings.closable) {
  263. $dimmer
  264. .dimmer('get dimmer')
  265. .on('click', module.hide)
  266. ;
  267. }
  268. },
  269. dimmerSettings: function() {
  270. module.debug('Setting dimmer settings', $dimmer);
  271. $dimmer
  272. .dimmer('setting', 'closable', false)
  273. .dimmer('setting', 'duration', {
  274. show : settings.duration * 0.95,
  275. hide : settings.duration * 1.05
  276. })
  277. .dimmer('setting', 'onShow' , module.add.keyboardShortcuts)
  278. // destory after changing settings in order to reattach events
  279. .dimmer('destroy')
  280. .dimmer('initialize')
  281. ;
  282. },
  283. scrolling: function() {
  284. $dimmer.addClass(className.scrolling);
  285. $module.addClass(className.scrolling);
  286. },
  287. type: function() {
  288. if(module.can.fit()) {
  289. module.verbose('Modal fits on screen');
  290. module.remove.scrolling();
  291. }
  292. else {
  293. module.verbose('Modal cannot fit on screen setting to scrolling');
  294. module.set.scrolling();
  295. }
  296. },
  297. position: function() {
  298. module.verbose('Centering modal on page', module.cache, module.cache.height / 2);
  299. if(module.can.fit()) {
  300. $module
  301. .css({
  302. top: '',
  303. marginTop: -(module.cache.height / 2)
  304. })
  305. ;
  306. }
  307. else {
  308. $module
  309. .css({
  310. marginTop : '1em',
  311. top : $document.scrollTop()
  312. })
  313. ;
  314. }
  315. }
  316. },
  317. setting: function(name, value) {
  318. if(value !== undefined) {
  319. if( $.isPlainObject(name) ) {
  320. $.extend(true, settings, name);
  321. }
  322. else {
  323. settings[name] = value;
  324. }
  325. }
  326. else {
  327. return settings[name];
  328. }
  329. },
  330. internal: function(name, value) {
  331. if(value !== undefined) {
  332. if( $.isPlainObject(name) ) {
  333. $.extend(true, module, name);
  334. }
  335. else {
  336. module[name] = value;
  337. }
  338. }
  339. else {
  340. return module[name];
  341. }
  342. },
  343. debug: function() {
  344. if(settings.debug) {
  345. if(settings.performance) {
  346. module.performance.log(arguments);
  347. }
  348. else {
  349. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  350. module.debug.apply(console, arguments);
  351. }
  352. }
  353. },
  354. verbose: function() {
  355. if(settings.verbose && settings.debug) {
  356. if(settings.performance) {
  357. module.performance.log(arguments);
  358. }
  359. else {
  360. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  361. module.verbose.apply(console, arguments);
  362. }
  363. }
  364. },
  365. error: function() {
  366. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  367. module.error.apply(console, arguments);
  368. },
  369. performance: {
  370. log: function(message) {
  371. var
  372. currentTime,
  373. executionTime,
  374. previousTime
  375. ;
  376. if(settings.performance) {
  377. currentTime = new Date().getTime();
  378. previousTime = time || currentTime;
  379. executionTime = currentTime - previousTime;
  380. time = currentTime;
  381. performance.push({
  382. 'Element' : element,
  383. 'Name' : message[0],
  384. 'Arguments' : [].slice.call(message, 1) || '',
  385. 'Execution Time' : executionTime
  386. });
  387. }
  388. clearTimeout(module.performance.timer);
  389. module.performance.timer = setTimeout(module.performance.display, 100);
  390. },
  391. display: function() {
  392. var
  393. title = settings.name + ':',
  394. totalTime = 0
  395. ;
  396. time = false;
  397. clearTimeout(module.performance.timer);
  398. $.each(performance, function(index, data) {
  399. totalTime += data['Execution Time'];
  400. });
  401. title += ' ' + totalTime + 'ms';
  402. if(moduleSelector) {
  403. title += ' \'' + moduleSelector + '\'';
  404. }
  405. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  406. console.groupCollapsed(title);
  407. if(console.table) {
  408. console.table(performance);
  409. }
  410. else {
  411. $.each(performance, function(index, data) {
  412. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  413. });
  414. }
  415. console.groupEnd();
  416. }
  417. performance = [];
  418. }
  419. },
  420. invoke: function(query, passedArguments, context) {
  421. var
  422. maxDepth,
  423. found,
  424. response
  425. ;
  426. passedArguments = passedArguments || queryArguments;
  427. context = element || context;
  428. if(typeof query == 'string' && instance !== undefined) {
  429. query = query.split(/[\. ]/);
  430. maxDepth = query.length - 1;
  431. $.each(query, function(depth, value) {
  432. var camelCaseValue = (depth != maxDepth)
  433. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  434. : query
  435. ;
  436. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  437. instance = instance[value];
  438. }
  439. else if( $.isPlainObject( instance[camelCaseValue] ) && (depth != maxDepth) ) {
  440. instance = instance[camelCaseValue];
  441. }
  442. else if( instance[value] !== undefined ) {
  443. found = instance[value];
  444. return false;
  445. }
  446. else if( instance[camelCaseValue] !== undefined ) {
  447. found = instance[camelCaseValue];
  448. return false;
  449. }
  450. else {
  451. module.error(error.method);
  452. return false;
  453. }
  454. });
  455. }
  456. if ( $.isFunction( found ) ) {
  457. response = found.apply(context, passedArguments);
  458. }
  459. else if(found !== undefined) {
  460. response = found;
  461. }
  462. if($.isArray(invokedResponse)) {
  463. invokedResponse.push(response);
  464. }
  465. else if(typeof invokedResponse == 'string') {
  466. invokedResponse = [invokedResponse, response];
  467. }
  468. else if(response !== undefined) {
  469. invokedResponse = response;
  470. }
  471. return found;
  472. }
  473. };
  474. if(methodInvoked) {
  475. if(instance === undefined) {
  476. module.initialize();
  477. }
  478. module.invoke(query);
  479. }
  480. else {
  481. if(instance !== undefined) {
  482. module.destroy();
  483. }
  484. module.initialize();
  485. }
  486. })
  487. ;
  488. return (invokedResponse !== undefined)
  489. ? invokedResponse
  490. : this
  491. ;
  492. };
  493. $.fn.modal.settings = {
  494. name : 'Modal',
  495. namespace : 'modal',
  496. verbose : true,
  497. debug : true,
  498. performance : true,
  499. closable : true,
  500. context : 'body',
  501. duration : 500,
  502. easing : 'easeOutExpo',
  503. offset : 0,
  504. transition : 'scale',
  505. onShow : function(){},
  506. onHide : function(){},
  507. selector : {
  508. close : '.close, .actions .button'
  509. },
  510. error : {
  511. method : 'The method you called is not defined.'
  512. },
  513. className : {
  514. active : 'active',
  515. scrolling : 'scrolling'
  516. },
  517. };
  518. })( jQuery, window , document );