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.

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