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.

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