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.

778 lines
22 KiB

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