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.

549 lines
15 KiB

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. 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.' + namespace)
  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', settings.duration * 0.75)
  262. .dimmer('setting', 'onShow' , module.add.keyboardShortcuts)
  263. .dimmer('setting', 'onHide', function() {
  264. module.hide();
  265. module.remove.keyboardShortcuts();
  266. })
  267. .dimmer('destroy')
  268. .dimmer('initialize')
  269. ;
  270. },
  271. scrolling: function() {
  272. $dimmer.addClass(className.scrolling);
  273. $module.addClass(className.scrolling);
  274. },
  275. type: function() {
  276. if(module.can.fit()) {
  277. module.verbose('Modal fits on screen');
  278. module.remove.scrolling();
  279. }
  280. else {
  281. module.verbose('Modal cannot fit on screen setting to scrolling');
  282. module.set.scrolling();
  283. }
  284. },
  285. position: function() {
  286. module.verbose('Centering modal on page', module.cache, module.cache.height / 2);
  287. if(module.can.fit()) {
  288. $module
  289. .css({
  290. top: '',
  291. marginTop: -(module.cache.height / 2)
  292. })
  293. ;
  294. }
  295. else {
  296. $module
  297. .css({
  298. marginTop : '1em',
  299. top : $document.scrollTop()
  300. })
  301. ;
  302. }
  303. }
  304. },
  305. setting: function(name, value) {
  306. if(value !== undefined) {
  307. if( $.isPlainObject(name) ) {
  308. $.extend(true, settings, name);
  309. }
  310. else {
  311. settings[name] = value;
  312. }
  313. }
  314. else {
  315. return settings[name];
  316. }
  317. },
  318. internal: function(name, value) {
  319. if(value !== undefined) {
  320. if( $.isPlainObject(name) ) {
  321. $.extend(true, module, name);
  322. }
  323. else {
  324. module[name] = value;
  325. }
  326. }
  327. else {
  328. return module[name];
  329. }
  330. },
  331. debug: function() {
  332. if(settings.debug) {
  333. if(settings.performance) {
  334. module.performance.log(arguments);
  335. }
  336. else {
  337. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  338. module.debug.apply(console, arguments);
  339. }
  340. }
  341. },
  342. verbose: function() {
  343. if(settings.verbose && settings.debug) {
  344. if(settings.performance) {
  345. module.performance.log(arguments);
  346. }
  347. else {
  348. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  349. module.verbose.apply(console, arguments);
  350. }
  351. }
  352. },
  353. error: function() {
  354. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  355. module.error.apply(console, arguments);
  356. },
  357. performance: {
  358. log: function(message) {
  359. var
  360. currentTime,
  361. executionTime,
  362. previousTime
  363. ;
  364. if(settings.performance) {
  365. currentTime = new Date().getTime();
  366. previousTime = time || currentTime;
  367. executionTime = currentTime - previousTime;
  368. time = currentTime;
  369. performance.push({
  370. 'Element' : element,
  371. 'Name' : message[0],
  372. 'Arguments' : [].slice.call(message, 1) || '',
  373. 'Execution Time' : executionTime
  374. });
  375. }
  376. clearTimeout(module.performance.timer);
  377. module.performance.timer = setTimeout(module.performance.display, 100);
  378. },
  379. display: function() {
  380. var
  381. title = settings.name + ':',
  382. totalTime = 0
  383. ;
  384. time = false;
  385. clearTimeout(module.performance.timer);
  386. $.each(performance, function(index, data) {
  387. totalTime += data['Execution Time'];
  388. });
  389. title += ' ' + totalTime + 'ms';
  390. if(moduleSelector) {
  391. title += ' \'' + moduleSelector + '\'';
  392. }
  393. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  394. console.groupCollapsed(title);
  395. if(console.table) {
  396. console.table(performance);
  397. }
  398. else {
  399. $.each(performance, function(index, data) {
  400. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  401. });
  402. }
  403. console.groupEnd();
  404. }
  405. performance = [];
  406. }
  407. },
  408. invoke: function(query, passedArguments, context) {
  409. var
  410. maxDepth,
  411. found,
  412. response
  413. ;
  414. passedArguments = passedArguments || queryArguments;
  415. context = element || context;
  416. if(typeof query == 'string' && instance !== undefined) {
  417. query = query.split(/[\. ]/);
  418. maxDepth = query.length - 1;
  419. $.each(query, function(depth, value) {
  420. var camelCaseValue = (depth != maxDepth)
  421. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  422. : query
  423. ;
  424. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  425. instance = instance[value];
  426. }
  427. else if( $.isPlainObject( instance[camelCaseValue] ) && (depth != maxDepth) ) {
  428. instance = instance[camelCaseValue];
  429. }
  430. else if( instance[value] !== undefined ) {
  431. found = instance[value];
  432. return false;
  433. }
  434. else if( instance[camelCaseValue] !== undefined ) {
  435. found = instance[camelCaseValue];
  436. return false;
  437. }
  438. else {
  439. module.error(error.method);
  440. return false;
  441. }
  442. });
  443. }
  444. if ( $.isFunction( found ) ) {
  445. response = found.apply(context, passedArguments);
  446. }
  447. else if(found !== undefined) {
  448. response = found;
  449. }
  450. if($.isArray(invokedResponse)) {
  451. invokedResponse.push(response);
  452. }
  453. else if(typeof invokedResponse == 'string') {
  454. invokedResponse = [invokedResponse, response];
  455. }
  456. else if(response !== undefined) {
  457. invokedResponse = response;
  458. }
  459. return found;
  460. }
  461. };
  462. if(methodInvoked) {
  463. if(instance === undefined) {
  464. module.initialize();
  465. }
  466. module.invoke(query);
  467. }
  468. else {
  469. if(instance !== undefined) {
  470. module.destroy();
  471. }
  472. module.initialize();
  473. }
  474. })
  475. ;
  476. return (invokedResponse !== undefined)
  477. ? invokedResponse
  478. : this
  479. ;
  480. };
  481. $.fn.modal.settings = {
  482. name : 'Modal',
  483. namespace : 'modal',
  484. verbose : true,
  485. debug : true,
  486. performance : true,
  487. closable : true,
  488. context : 'body',
  489. duration : 600,
  490. easing : 'easeOutExpo',
  491. offset : 0,
  492. transition : 'scale',
  493. onShow : function(){},
  494. onHide : function(){},
  495. selector : {
  496. close : '.close, .actions .button'
  497. },
  498. error : {
  499. method : 'The method you called is not defined.'
  500. },
  501. className : {
  502. active : 'active',
  503. scrolling : 'scrolling'
  504. },
  505. };
  506. })( jQuery, window , document );