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.

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