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.

592 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('add content', $module)
  53. ;
  54. $dimmer = $context
  55. .dimmer('get dimmer')
  56. ;
  57. module.verbose('Attaching close events', $close);
  58. $close
  59. .on('click' + eventNamespace, module.event.close)
  60. ;
  61. $window
  62. .on('resize', function() {
  63. module.event.debounce(module.refresh, 50);
  64. })
  65. ;
  66. module.instantiate();
  67. },
  68. instantiate: function() {
  69. module.verbose('Storing instance of modal');
  70. instance = module;
  71. $module
  72. .data(moduleNamespace, instance)
  73. ;
  74. },
  75. destroy: function() {
  76. module.verbose('Destroying previous modal');
  77. $module
  78. .removeData(moduleNamespace)
  79. .off(eventNamespace)
  80. ;
  81. $close
  82. .off(eventNamespace)
  83. ;
  84. $context
  85. .dimmer('destroy')
  86. ;
  87. },
  88. refresh: function() {
  89. module.remove.scrolling();
  90. module.cacheSizes();
  91. module.set.type();
  92. module.set.position();
  93. },
  94. attachEvents: function(selector, event) {
  95. var
  96. $toggle = $(selector)
  97. ;
  98. event = $.isFunction(module[event])
  99. ? module[event]
  100. : module.toggle
  101. ;
  102. if($toggle.size() > 0) {
  103. module.debug('Attaching modal events to element', selector, event);
  104. $toggle
  105. .off(eventNamespace)
  106. .on('click' + eventNamespace, event)
  107. ;
  108. }
  109. else {
  110. module.error(error.notFound);
  111. }
  112. },
  113. event: {
  114. close: function() {
  115. module.verbose('Closing element pressed');
  116. if( $(this).is(selector.approve) ) {
  117. $.proxy(settings.onApprove, element)();
  118. }
  119. if( $(this).is(selector.deny) ) {
  120. $.proxy(settings.onDeny, element)();
  121. }
  122. module.hide();
  123. },
  124. click: function(event) {
  125. module.verbose('Determining if event occured on dimmer', event);
  126. if( $dimmer.find(event.target).size() === 0 ) {
  127. module.hide();
  128. event.stopImmediatePropagation();
  129. }
  130. },
  131. debounce: function(method, delay) {
  132. clearTimeout(module.timer);
  133. module.timer = setTimeout(method, delay);
  134. },
  135. keyboard: function(event) {
  136. var
  137. keyCode = event.which,
  138. escapeKey = 27
  139. ;
  140. if(keyCode == escapeKey) {
  141. module.debug('Escape key pressed hiding modal');
  142. module.hide();
  143. event.preventDefault();
  144. }
  145. },
  146. resize: function() {
  147. if( $dimmable.dimmer('is active') ) {
  148. module.refresh();
  149. }
  150. }
  151. },
  152. toggle: function() {
  153. if( module.is.active() ) {
  154. module.hide();
  155. }
  156. else {
  157. module.show();
  158. }
  159. },
  160. show: function() {
  161. module.showDimmer();
  162. module.cacheSizes();
  163. module.set.position();
  164. module.hideAll();
  165. if(settings.transition && $.fn.transition !== undefined) {
  166. $module
  167. .transition(settings.transition + ' in', settings.duration, module.set.active)
  168. ;
  169. }
  170. else {
  171. $module
  172. .fadeIn(settings.duration, settings.easing, module.set.active)
  173. ;
  174. }
  175. module.debug('Triggering dimmer');
  176. $.proxy(settings.onShow, element)();
  177. },
  178. showDimmer: function() {
  179. module.debug('Showing modal');
  180. module.set.dimmerSettings();
  181. $dimmable.dimmer('show');
  182. },
  183. hide: function() {
  184. if(settings.closable) {
  185. $dimmer
  186. .off('click' + eventNamespace)
  187. ;
  188. }
  189. if( $dimmable.dimmer('is active') ) {
  190. $dimmable.dimmer('hide');
  191. }
  192. if( module.is.active() ) {
  193. module.hideModal();
  194. $.proxy(settings.onHide, element)();
  195. }
  196. else {
  197. module.debug('Cannot hide modal, modal is not visible');
  198. }
  199. },
  200. hideDimmer: function() {
  201. module.debug('Hiding dimmer');
  202. $dimmable.dimmer('hide');
  203. },
  204. hideModal: function() {
  205. module.debug('Hiding modal');
  206. module.remove.keyboardShortcuts();
  207. if(settings.transition && $.fn.transition !== undefined) {
  208. $module
  209. .transition(settings.transition + ' out', settings.duration, function() {
  210. module.remove.active();
  211. module.restore.focus();
  212. })
  213. ;
  214. }
  215. else {
  216. $module
  217. .fadeOut(settings.duration, settings.easing, function() {
  218. module.remove.active();
  219. module.restore.focus();
  220. })
  221. ;
  222. }
  223. },
  224. hideAll: function() {
  225. $otherModals
  226. .filter(':visible')
  227. .modal('hide')
  228. ;
  229. },
  230. add: {
  231. keyboardShortcuts: function() {
  232. module.verbose('Adding keyboard shortcuts');
  233. $document
  234. .on('keyup' + eventNamespace, module.event.keyboard)
  235. ;
  236. }
  237. },
  238. save: {
  239. focus: function() {
  240. $focusedElement = $(document.activeElement).blur();
  241. }
  242. },
  243. restore: {
  244. focus: function() {
  245. if($focusedElement.size() > 0) {
  246. $focusedElement.focus();
  247. }
  248. }
  249. },
  250. remove: {
  251. active: function() {
  252. $module.removeClass(className.active);
  253. },
  254. keyboardShortcuts: function() {
  255. module.verbose('Removing keyboard shortcuts');
  256. $document
  257. .off('keyup' + eventNamespace)
  258. ;
  259. },
  260. scrolling: function() {
  261. $dimmable.removeClass(className.scrolling);
  262. $module.removeClass(className.scrolling);
  263. }
  264. },
  265. cacheSizes: function() {
  266. module.cache = {
  267. height : $module.outerHeight() + settings.offset,
  268. contextHeight : (settings.context == 'body')
  269. ? $(window).height()
  270. : $dimmable.height()
  271. };
  272. module.debug('Caching modal and container sizes', module.cache);
  273. },
  274. can: {
  275. fit: function() {
  276. return (module.cache.height < module.cache.contextHeight);
  277. }
  278. },
  279. is: {
  280. active: function() {
  281. return $module.hasClass(className.active);
  282. }
  283. },
  284. set: {
  285. active: function() {
  286. module.add.keyboardShortcuts();
  287. module.save.focus();
  288. module.set.type();
  289. $module
  290. .addClass(className.active)
  291. ;
  292. if(settings.closable) {
  293. $dimmer
  294. .on('click' + eventNamespace, module.event.click)
  295. ;
  296. }
  297. },
  298. dimmerSettings: function() {
  299. module.debug('Setting dimmer settings', $dimmable);
  300. $dimmable
  301. .dimmer({
  302. closable: false,
  303. show: settings.duration * 0.95,
  304. hide: settings.duration * 1.05
  305. })
  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. onApprove : function(){ console.log('approved'); },
  533. onDeny : function(){ console.log('denied'); },
  534. selector : {
  535. close : '.close, .actions .button',
  536. approve : '.actions .positive, .actions .approve',
  537. deny : '.actions .negative, .actions .cancel'
  538. },
  539. error : {
  540. method : 'The method you called is not defined.'
  541. },
  542. className : {
  543. active : 'active',
  544. scrolling : 'scrolling'
  545. },
  546. };
  547. })( jQuery, window , document );