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.

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