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.

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