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.

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