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.

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