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.

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