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.

471 lines
13 KiB

  1. /* ******************************
  2. Modal
  3. Author: Jack Lukic
  4. Notes: First Commit May 14, 2012
  5. Manages modal state and
  6. stage dimming
  7. ****************************** */
  8. ;(function ( $, window, document, undefined ) {
  9. $.fn.modal = function(parameters) {
  10. var
  11. $allModals = $(this),
  12. $document = $(document),
  13. settings = ( $.isPlainObject(parameters) )
  14. ? $.extend(true, {}, $.fn.modal.settings, parameters)
  15. : $.fn.modal.settings,
  16. selector = settings.selector,
  17. className = settings.className,
  18. namespace = settings.namespace,
  19. error = settings.error,
  20. eventNamespace = '.' + namespace,
  21. moduleNamespace = 'module-' + namespace,
  22. moduleSelector = $allModals.selector || '',
  23. time = new Date().getTime(),
  24. performance = [],
  25. query = arguments[0],
  26. methodInvoked = (typeof query == 'string'),
  27. queryArguments = [].slice.call(arguments, 1),
  28. invokedResponse
  29. ;
  30. $allModals
  31. .each(function() {
  32. var
  33. $modal = $(this),
  34. $context = $(settings.context),
  35. $otherModals = $allModals.not($modal),
  36. $closeButton = $modal.find(selector.closeButton),
  37. $dimmer,
  38. element = this,
  39. instance = $modal.data(moduleNamespace),
  40. modal
  41. ;
  42. modal = {
  43. initialize: function() {
  44. modal.verbose('Initializing modal');
  45. $closeButton
  46. .on('click', modal.event.close)
  47. ;
  48. $context
  49. .dimmer({
  50. closable: settings.closable,
  51. duration: settings.duration,
  52. onShow: function() {
  53. modal.add.keyboardShortcuts();
  54. $.proxy(settings.onShow, this)();
  55. },
  56. onHide: function() {
  57. if($modal.is(':visible')) {
  58. $context.off('.dimmer');
  59. modal.hide();
  60. $.proxy(settings.onHide, this)();
  61. }
  62. modal.remove.keyboardShortcuts();
  63. }
  64. })
  65. ;
  66. $dimmer = $context.children(selector.dimmer);
  67. if( $modal.parent()[0] !== $dimmer[0] ) {
  68. modal.debug('Moving element inside dimmer', $context);
  69. $modal
  70. .detach()
  71. .appendTo($dimmer)
  72. ;
  73. }
  74. modal.cache.sizes();
  75. modal.instantiate();
  76. },
  77. instantiate: function() {
  78. modal.verbose('Storing instance of modal');
  79. instance = modal;
  80. $modal
  81. .data(moduleNamespace, instance)
  82. ;
  83. },
  84. destroy: function() {
  85. modal.verbose('Destroying previous modal');
  86. $modal
  87. .off(eventNamespace)
  88. ;
  89. },
  90. event: {
  91. close: function() {
  92. modal.verbose('Close button pressed');
  93. $context.dimmer('hide');
  94. },
  95. keyboard: function(event) {
  96. var
  97. keyCode = event.which,
  98. escapeKey = 27
  99. ;
  100. if(keyCode == escapeKey) {
  101. modal.debug('Escape key pressed hiding modal');
  102. $context.dimmer('hide');
  103. event.preventDefault();
  104. }
  105. },
  106. resize: function() {
  107. modal.cache.sizes();
  108. if( $modal.is(':visible') ) {
  109. modal.set.type();
  110. modal.set.position();
  111. }
  112. }
  113. },
  114. toggle: function() {
  115. if( modal.is.active() ) {
  116. modal.hide();
  117. }
  118. else {
  119. modal.show();
  120. }
  121. },
  122. show: function() {
  123. modal.set.type();
  124. modal.set.position();
  125. modal.hideAll();
  126. if(settings.transition && $.fn.transition !== undefined) {
  127. $modal
  128. .transition(settings.transition + ' in', settings.duration, modal.set.active)
  129. ;
  130. }
  131. else {
  132. $modal
  133. .fadeIn(settings.duration, settings.easing, modal.set.active)
  134. ;
  135. }
  136. $context.dimmer('show');
  137. },
  138. hide: function() {
  139. // remove keyboard detection
  140. $document
  141. .off('keyup.' + namespace)
  142. ;
  143. if(settings.transition && $.fn.transition !== undefined) {
  144. $modal
  145. .transition(settings.transition + ' out', settings.duration, modal.remove.active)
  146. ;
  147. }
  148. else {
  149. $modal
  150. .fadeOut(settings.duration, settings.easing, modal.remove.active)
  151. ;
  152. }
  153. },
  154. hideAll: function() {
  155. $otherModals
  156. .filter(':visible')
  157. .modal('hide')
  158. ;
  159. },
  160. add: {
  161. keyboardShortcuts: function() {
  162. modal.verbose('Adding keyboard shortcuts');
  163. $document
  164. .on('keyup' + eventNamespace, modal.event.keyboard)
  165. ;
  166. }
  167. },
  168. remove: {
  169. active: function() {
  170. $modal.removeClass(className.active);
  171. },
  172. keyboardShortcuts: function() {
  173. modal.verbose('Removing keyboard shortcuts');
  174. $document
  175. .off('keyup' + eventNamespace)
  176. ;
  177. }
  178. },
  179. cache: {
  180. sizes: function() {
  181. modal.cache = {
  182. height : $modal.outerHeight() + settings.offset,
  183. contextHeight : (settings.context == 'body')
  184. ? $(window).height()
  185. : $context.height()
  186. };
  187. modal.debug('Caching modal and container sizes', modal.cache);
  188. }
  189. },
  190. can: {
  191. fit: function() {
  192. return (modal.cache.height < modal.cache.contextHeight);
  193. }
  194. },
  195. is: {
  196. active: function() {
  197. return $modal.hasClass(className.active);
  198. }
  199. },
  200. set: {
  201. active: function() {
  202. $modal.addClass('active');
  203. },
  204. type: function() {
  205. if(modal.can.fit()) {
  206. modal.verbose('Modal fits on screen');
  207. $modal.removeClass(className.scrolling);
  208. }
  209. else {
  210. modal.verbose('Modal cannot fit on screen setting to scrolling');
  211. $modal.addClass(className.scrolling);
  212. }
  213. },
  214. position: function() {
  215. modal.verbose('Centering modal on page');
  216. if(modal.can.fit()) {
  217. $modal
  218. .css({
  219. marginTop: -(modal.cache.height / 2)
  220. })
  221. ;
  222. }
  223. else {
  224. $modal
  225. .css({
  226. top: $context.prop('scrollTop')
  227. })
  228. ;
  229. }
  230. }
  231. },
  232. setting: function(name, value) {
  233. if(value !== undefined) {
  234. if( $.isPlainObject(name) ) {
  235. $.extend(true, settings, name);
  236. }
  237. else {
  238. settings[name] = value;
  239. }
  240. }
  241. else {
  242. return settings[name];
  243. }
  244. },
  245. internal: function(name, value) {
  246. if(value !== undefined) {
  247. if( $.isPlainObject(name) ) {
  248. $.extend(true, modal, name);
  249. }
  250. else {
  251. modal[name] = value;
  252. }
  253. }
  254. else {
  255. return modal[name];
  256. }
  257. },
  258. debug: function() {
  259. if(settings.debug) {
  260. if(settings.performance) {
  261. modal.performance.log(arguments);
  262. }
  263. else {
  264. modal.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  265. modal.debug.apply(console, arguments);
  266. }
  267. }
  268. },
  269. verbose: function() {
  270. if(settings.verbose && settings.debug) {
  271. if(settings.performance) {
  272. modal.performance.log(arguments);
  273. }
  274. else {
  275. modal.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  276. modal.verbose.apply(console, arguments);
  277. }
  278. }
  279. },
  280. error: function() {
  281. modal.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  282. modal.error.apply(console, arguments);
  283. },
  284. performance: {
  285. log: function(message) {
  286. var
  287. currentTime,
  288. executionTime,
  289. previousTime
  290. ;
  291. if(settings.performance) {
  292. currentTime = new Date().getTime();
  293. previousTime = time || currentTime;
  294. executionTime = currentTime - previousTime;
  295. time = currentTime;
  296. performance.push({
  297. 'Element' : element,
  298. 'Name' : message[0],
  299. 'Arguments' : [].slice.call(message, 1) || '',
  300. 'Execution Time' : executionTime
  301. });
  302. }
  303. clearTimeout(modal.performance.timer);
  304. modal.performance.timer = setTimeout(modal.performance.display, 100);
  305. },
  306. display: function() {
  307. var
  308. title = settings.name + ':',
  309. totalTime = 0
  310. ;
  311. time = false;
  312. clearTimeout(modal.performance.timer);
  313. $.each(performance, function(index, data) {
  314. totalTime += data['Execution Time'];
  315. });
  316. title += ' ' + totalTime + 'ms';
  317. if(moduleSelector) {
  318. title += ' \'' + moduleSelector + '\'';
  319. }
  320. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  321. console.groupCollapsed(title);
  322. if(console.table) {
  323. console.table(performance);
  324. }
  325. else {
  326. $.each(performance, function(index, data) {
  327. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  328. });
  329. }
  330. console.groupEnd();
  331. }
  332. performance = [];
  333. }
  334. },
  335. invoke: function(query, passedArguments, context) {
  336. var
  337. maxDepth,
  338. found,
  339. response
  340. ;
  341. passedArguments = passedArguments || queryArguments;
  342. context = element || context;
  343. if(typeof query == 'string' && instance !== undefined) {
  344. query = query.split(/[\. ]/);
  345. maxDepth = query.length - 1;
  346. $.each(query, function(depth, value) {
  347. var camelCaseValue = (depth != maxDepth)
  348. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  349. : query
  350. ;
  351. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  352. instance = instance[value];
  353. }
  354. else if( $.isPlainObject( instance[camelCaseValue] ) && (depth != maxDepth) ) {
  355. instance = instance[camelCaseValue];
  356. }
  357. else if( instance[value] !== undefined ) {
  358. found = instance[value];
  359. return false;
  360. }
  361. else if( instance[camelCaseValue] !== undefined ) {
  362. found = instance[camelCaseValue];
  363. return false;
  364. }
  365. else {
  366. modal.error(error.method);
  367. return false;
  368. }
  369. });
  370. }
  371. if ( $.isFunction( found ) ) {
  372. response = found.apply(context, passedArguments);
  373. }
  374. else if(found !== undefined) {
  375. response = found;
  376. }
  377. if($.isArray(invokedResponse)) {
  378. invokedResponse.push(response);
  379. }
  380. else if(typeof invokedResponse == 'string') {
  381. invokedResponse = [invokedResponse, response];
  382. }
  383. else if(response !== undefined) {
  384. invokedResponse = response;
  385. }
  386. return found;
  387. }
  388. };
  389. if(methodInvoked) {
  390. if(instance === undefined) {
  391. modal.initialize();
  392. }
  393. invokedResponse = modal.invoke(query);
  394. }
  395. else {
  396. if(instance !== undefined) {
  397. modal.destroy();
  398. }
  399. modal.initialize();
  400. }
  401. })
  402. ;
  403. return (invokedResponse !== undefined)
  404. ? invokedResponse
  405. : this
  406. ;
  407. };
  408. $.fn.modal.settings = {
  409. name : 'Modal',
  410. namespace : 'modal',
  411. verbose : true,
  412. debug : true,
  413. performance : true,
  414. offset : 0,
  415. transition : 'scale',
  416. duration : 500,
  417. easing : 'easeOutExpo',
  418. closable : true,
  419. context : 'body',
  420. onShow : function(){},
  421. onHide : function(){},
  422. selector : {
  423. closeButton : '.close, .actions .button',
  424. dimmer: '.ui.dimmer'
  425. },
  426. error : {
  427. method : 'The method you called is not defined.'
  428. },
  429. className : {
  430. scrolling : 'scrolling'
  431. },
  432. };
  433. })( jQuery, window , document );