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.

682 lines
20 KiB

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