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.

1288 lines
41 KiB

10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /*!
  2. * # Semantic UI 2.0.0 - Popup
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2015 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ($, window, document, undefined) {
  12. "use strict";
  13. $.fn.popup = function(parameters) {
  14. var
  15. $allModules = $(this),
  16. $document = $(document),
  17. moduleSelector = $allModules.selector || '',
  18. hasTouch = ('ontouchstart' in document.documentElement),
  19. time = new Date().getTime(),
  20. performance = [],
  21. query = arguments[0],
  22. methodInvoked = (typeof query == 'string'),
  23. queryArguments = [].slice.call(arguments, 1),
  24. returnedValue
  25. ;
  26. $allModules
  27. .each(function() {
  28. var
  29. settings = ( $.isPlainObject(parameters) )
  30. ? $.extend(true, {}, $.fn.popup.settings, parameters)
  31. : $.extend({}, $.fn.popup.settings),
  32. selector = settings.selector,
  33. className = settings.className,
  34. error = settings.error,
  35. metadata = settings.metadata,
  36. namespace = settings.namespace,
  37. eventNamespace = '.' + settings.namespace,
  38. moduleNamespace = 'module-' + namespace,
  39. $module = $(this),
  40. $context = $(settings.context),
  41. $target = (settings.target)
  42. ? $(settings.target)
  43. : $module,
  44. $window = $(window),
  45. $body = $('body'),
  46. $popup,
  47. $offsetParent,
  48. searchDepth = 0,
  49. triedPositions = false,
  50. element = this,
  51. instance = $module.data(moduleNamespace),
  52. elementNamespace,
  53. id,
  54. module
  55. ;
  56. module = {
  57. // binds events
  58. initialize: function() {
  59. module.debug('Initializing', $module);
  60. module.createID();
  61. module.bind.events();
  62. if( !module.exists() && settings.preserve) {
  63. module.create();
  64. }
  65. module.instantiate();
  66. },
  67. instantiate: function() {
  68. module.verbose('Storing instance', module);
  69. instance = module;
  70. $module
  71. .data(moduleNamespace, instance)
  72. ;
  73. },
  74. refresh: function() {
  75. if(settings.popup) {
  76. $popup = $(settings.popup).eq(0);
  77. }
  78. else {
  79. if(settings.inline) {
  80. $popup = $target.next(selector.popup).eq(0);
  81. settings.popup = $popup;
  82. }
  83. }
  84. if(settings.popup) {
  85. $popup.addClass(className.loading);
  86. $offsetParent = module.get.offsetParent();
  87. $popup.removeClass(className.loading);
  88. if(settings.movePopup && module.has.popup() && module.get.offsetParent($popup)[0] !== $offsetParent[0]) {
  89. module.debug('Moving popup to the same offset parent as activating element');
  90. $popup
  91. .detach()
  92. .appendTo($offsetParent)
  93. ;
  94. }
  95. }
  96. else {
  97. $offsetParent = (settings.inline)
  98. ? module.get.offsetParent($target)
  99. : module.has.popup()
  100. ? module.get.offsetParent($popup)
  101. : $body
  102. ;
  103. }
  104. if( $offsetParent.is('html') ) {
  105. module.debug('Setting page as offset parent');
  106. $offsetParent = $body;
  107. }
  108. },
  109. reposition: function() {
  110. module.refresh();
  111. module.set.position();
  112. },
  113. destroy: function() {
  114. module.debug('Destroying previous module');
  115. // remove element only if was created dynamically
  116. if($popup && !settings.preserve) {
  117. module.removePopup();
  118. }
  119. // clear all timeouts
  120. clearTimeout(module.hideTimer);
  121. clearTimeout(module.showTimer);
  122. // remove events
  123. $window.off(elementNamespace);
  124. $module
  125. .off(eventNamespace)
  126. .removeData(moduleNamespace)
  127. ;
  128. },
  129. event: {
  130. start: function(event) {
  131. var
  132. delay = ($.isPlainObject(settings.delay))
  133. ? settings.delay.show
  134. : settings.delay
  135. ;
  136. clearTimeout(module.hideTimer);
  137. module.showTimer = setTimeout(module.show, delay);
  138. },
  139. end: function() {
  140. var
  141. delay = ($.isPlainObject(settings.delay))
  142. ? settings.delay.hide
  143. : settings.delay
  144. ;
  145. clearTimeout(module.showTimer);
  146. module.hideTimer = setTimeout(module.hide, delay);
  147. },
  148. resize: function() {
  149. if( module.is.visible() ) {
  150. module.set.position();
  151. }
  152. }
  153. },
  154. // generates popup html from metadata
  155. create: function() {
  156. var
  157. html = module.get.html(),
  158. variation = module.get.variation(),
  159. title = module.get.title(),
  160. content = module.get.content()
  161. ;
  162. if(html || content || title) {
  163. module.debug('Creating pop-up html');
  164. if(!html) {
  165. html = settings.templates.popup({
  166. title : title,
  167. content : content
  168. });
  169. }
  170. $popup = $('<div/>')
  171. .addClass(className.popup)
  172. .addClass(variation)
  173. .data(metadata.activator, $module)
  174. .html(html)
  175. ;
  176. if(variation) {
  177. $popup
  178. .addClass(variation)
  179. ;
  180. }
  181. if(settings.inline) {
  182. module.verbose('Inserting popup element inline', $popup);
  183. $popup
  184. .insertAfter($module)
  185. ;
  186. }
  187. else {
  188. module.verbose('Appending popup element to body', $popup);
  189. $popup
  190. .appendTo( $context )
  191. ;
  192. }
  193. module.refresh();
  194. if(settings.hoverable) {
  195. module.bind.popup();
  196. }
  197. settings.onCreate.call($popup, element);
  198. }
  199. else if($target.next(selector.popup).length !== 0) {
  200. module.verbose('Pre-existing popup found');
  201. settings.inline = true;
  202. settings.popups = $target.next(selector.popup).data(metadata.activator, $module);
  203. module.refresh();
  204. if(settings.hoverable) {
  205. module.bind.popup();
  206. }
  207. }
  208. else if(settings.popup) {
  209. $(settings.popup).data(metadata.activator, $module);
  210. module.verbose('Used popup specified in settings');
  211. module.refresh();
  212. if(settings.hoverable) {
  213. module.bind.popup();
  214. }
  215. }
  216. else {
  217. module.debug('No content specified skipping display', element);
  218. }
  219. },
  220. createID: function() {
  221. id = (Math.random().toString(16) + '000000000').substr(2,8);
  222. elementNamespace = '.' + id;
  223. module.verbose('Creating unique id for element', id);
  224. },
  225. // determines popup state
  226. toggle: function() {
  227. module.debug('Toggling pop-up');
  228. if( module.is.hidden() ) {
  229. module.debug('Popup is hidden, showing pop-up');
  230. module.unbind.close();
  231. module.show();
  232. }
  233. else {
  234. module.debug('Popup is visible, hiding pop-up');
  235. module.hide();
  236. }
  237. },
  238. show: function(callback) {
  239. callback = $.isFunction(callback) ? callback : function(){};
  240. module.debug('Showing pop-up', settings.transition);
  241. if(module.is.hidden() && !( module.is.active() && module.is.dropdown()) ) {
  242. if( !module.exists() ) {
  243. module.create();
  244. }
  245. if(settings.onShow.call($popup, element) === false) {
  246. module.debug('onShow callback returned false, cancelling popup animation');
  247. return;
  248. }
  249. else if(!settings.preserve && !settings.popup) {
  250. module.refresh();
  251. }
  252. if( $popup && module.set.position() ) {
  253. module.save.conditions();
  254. if(settings.exclusive) {
  255. module.hideAll();
  256. }
  257. module.animate.show(callback);
  258. }
  259. }
  260. },
  261. hide: function(callback) {
  262. callback = $.isFunction(callback) ? callback : function(){};
  263. if( module.is.visible() || module.is.animating() ) {
  264. if(settings.onHide.call($popup, element) === false) {
  265. module.debug('onHide callback returned false, cancelling popup animation');
  266. return;
  267. }
  268. module.remove.visible();
  269. module.unbind.close();
  270. module.restore.conditions();
  271. module.animate.hide(callback);
  272. }
  273. },
  274. hideAll: function() {
  275. $(selector.popup)
  276. .filter('.' + className.visible)
  277. .each(function() {
  278. $(this)
  279. .data(metadata.activator)
  280. .popup('hide')
  281. ;
  282. })
  283. ;
  284. },
  285. hideGracefully: function(event) {
  286. // don't close on clicks inside popup
  287. if(event && $(event.target).closest(selector.popup).length === 0) {
  288. module.debug('Click occurred outside popup hiding popup');
  289. module.hide();
  290. }
  291. else {
  292. module.debug('Click was inside popup, keeping popup open');
  293. }
  294. },
  295. exists: function() {
  296. if(!$popup) {
  297. return false;
  298. }
  299. if(settings.inline || settings.popup) {
  300. return ( module.has.popup() );
  301. }
  302. else {
  303. return ( $popup.closest($context).length >= 1 )
  304. ? true
  305. : false
  306. ;
  307. }
  308. },
  309. removePopup: function() {
  310. module.debug('Removing popup', $popup);
  311. if( module.has.popup() && !settings.popup) {
  312. $popup.remove();
  313. $popup = undefined;
  314. }
  315. settings.onRemove.call($popup, element);
  316. },
  317. save: {
  318. conditions: function() {
  319. module.cache = {
  320. title: $module.attr('title')
  321. };
  322. if (module.cache.title) {
  323. $module.removeAttr('title');
  324. }
  325. module.verbose('Saving original attributes', module.cache.title);
  326. }
  327. },
  328. restore: {
  329. conditions: function() {
  330. if(module.cache && module.cache.title) {
  331. $module.attr('title', module.cache.title);
  332. module.verbose('Restoring original attributes', module.cache.title);
  333. }
  334. return true;
  335. }
  336. },
  337. animate: {
  338. show: function(callback) {
  339. callback = $.isFunction(callback) ? callback : function(){};
  340. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  341. module.set.visible();
  342. $popup
  343. .transition({
  344. animation : settings.transition + ' in',
  345. queue : false,
  346. debug : settings.debug,
  347. verbose : settings.verbose,
  348. duration : settings.duration,
  349. onComplete : function() {
  350. //module.bind.close();
  351. //callback.call($popup, element);
  352. //settings.onVisible.call($popup, element);
  353. }
  354. })
  355. ;
  356. }
  357. else {
  358. module.error(error.noTransition);
  359. }
  360. },
  361. hide: function(callback) {
  362. callback = $.isFunction(callback) ? callback : function(){};
  363. module.debug('Hiding pop-up');
  364. if(settings.onShow.call($popup, element) === false) {
  365. module.debug('onShow callback returned false, cancelling popup animation');
  366. return;
  367. }
  368. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  369. $popup
  370. .transition({
  371. animation : settings.transition + ' out',
  372. queue : false,
  373. duration : settings.duration,
  374. debug : settings.debug,
  375. verbose : settings.verbose,
  376. onComplete : function() {
  377. module.reset();
  378. callback.call($popup, element);
  379. settings.onHidden.call($popup, element);
  380. }
  381. })
  382. ;
  383. }
  384. else {
  385. module.error(error.noTransition);
  386. }
  387. }
  388. },
  389. get: {
  390. html: function() {
  391. $module.removeData(metadata.html);
  392. return $module.data(metadata.html) || settings.html;
  393. },
  394. title: function() {
  395. $module.removeData(metadata.title);
  396. return $module.data(metadata.title) || settings.title;
  397. },
  398. content: function() {
  399. $module.removeData(metadata.content);
  400. return $module.data(metadata.content) || $module.attr('title') || settings.content;
  401. },
  402. variation: function() {
  403. $module.removeData(metadata.variation);
  404. return $module.data(metadata.variation) || settings.variation;
  405. },
  406. id: function() {
  407. return id;
  408. },
  409. startEvent: function() {
  410. if(settings.on == 'hover') {
  411. return (hasTouch && settings.addTouchEvents)
  412. ? 'touchstart mouseenter'
  413. : 'mouseenter'
  414. ;
  415. }
  416. else if(settings.on == 'focus') {
  417. return 'focus';
  418. }
  419. return false;
  420. },
  421. scrollEvent: function() {
  422. return (hasTouch && settings.addTouchEvents)
  423. ? 'touchmove scroll'
  424. : 'scroll'
  425. ;
  426. },
  427. endEvent: function() {
  428. if(settings.on == 'hover') {
  429. return 'mouseleave';
  430. }
  431. else if(settings.on == 'focus') {
  432. return 'blur';
  433. }
  434. return false;
  435. },
  436. offsetParent: function($target) {
  437. var
  438. element = ($target !== undefined)
  439. ? $target[0]
  440. : $module[0],
  441. parentNode = element.parentNode,
  442. $node = $(parentNode)
  443. ;
  444. if(parentNode) {
  445. var
  446. is2D = ($node.css('transform') === 'none'),
  447. isStatic = ($node.css('position') === 'static'),
  448. isHTML = $node.is('html')
  449. ;
  450. while(parentNode && !isHTML && isStatic && is2D) {
  451. parentNode = parentNode.parentNode;
  452. $node = $(parentNode);
  453. is2D = ($node.css('transform') === 'none');
  454. isStatic = ($node.css('position') === 'static');
  455. isHTML = $node.is('html');
  456. }
  457. }
  458. return ($node && $node.length > 0)
  459. ? $node
  460. : $()
  461. ;
  462. },
  463. offstagePosition: function(position) {
  464. var
  465. screen = {
  466. top : $(window).scrollTop(),
  467. left : $(window).scrollLeft(),
  468. width : $(window).width(),
  469. height : $(window).height()
  470. },
  471. boundary = {
  472. top : screen.top,
  473. bottom : screen.top + screen.height,
  474. left : screen.left,
  475. right : screen.left + screen.width
  476. },
  477. popup = {
  478. width : $popup.width(),
  479. height : $popup.height(),
  480. offset : $popup.offset()
  481. },
  482. offstage = {},
  483. offstagePositions = []
  484. ;
  485. position = position || false;
  486. if(popup.offset && position) {
  487. module.verbose('Checking if outside viewable area', popup.offset);
  488. offstage = {
  489. top : (popup.offset.top < boundary.top),
  490. bottom : (popup.offset.top + popup.height > boundary.bottom),
  491. right : (popup.offset.left + popup.width > boundary.right),
  492. left : (popup.offset.left < boundary.left)
  493. };
  494. }
  495. // return only boundaries that have been surpassed
  496. $.each(offstage, function(direction, isOffstage) {
  497. if(isOffstage) {
  498. offstagePositions.push(direction);
  499. }
  500. });
  501. return (offstagePositions.length > 0)
  502. ? offstagePositions.join(' ')
  503. : false
  504. ;
  505. },
  506. positions: function() {
  507. return {
  508. 'top left' : false,
  509. 'top center' : false,
  510. 'top right' : false,
  511. 'bottom left' : false,
  512. 'bottom center' : false,
  513. 'bottom right' : false,
  514. 'left center' : false,
  515. 'right center' : false
  516. };
  517. },
  518. nextPosition: function(position) {
  519. var
  520. positions = position.split(' '),
  521. verticalPosition = positions[0],
  522. horizontalPosition = positions[1],
  523. opposite = {
  524. top : 'bottom',
  525. bottom : 'top',
  526. left : 'right',
  527. right : 'left'
  528. },
  529. adjacent = {
  530. left : 'center',
  531. center : 'right',
  532. right : 'left'
  533. },
  534. backup = {
  535. 'top left' : 'top center',
  536. 'top center' : 'top right',
  537. 'top right' : 'right center',
  538. 'right center' : 'bottom right',
  539. 'bottom right' : 'bottom center',
  540. 'bottom center' : 'bottom left',
  541. 'bottom left' : 'left center',
  542. 'left center' : 'top left'
  543. },
  544. adjacentsAvailable = (verticalPosition == 'top' || verticalPosition == 'bottom'),
  545. oppositeTried = false,
  546. adjacentTried = false,
  547. nextPosition = false
  548. ;
  549. if(!triedPositions) {
  550. module.verbose('All available positions available');
  551. triedPositions = module.get.positions();
  552. }
  553. module.debug('Recording last position tried', position);
  554. triedPositions[position] = true;
  555. if(settings.prefer === 'opposite') {
  556. nextPosition = [opposite[verticalPosition], horizontalPosition];
  557. nextPosition = nextPosition.join(' ');
  558. oppositeTried = (triedPositions[nextPosition] === true);
  559. module.debug('Trying opposite strategy', nextPosition);
  560. }
  561. if((settings.prefer === 'adjacent') && adjacentsAvailable ) {
  562. nextPosition = [verticalPosition, adjacent[horizontalPosition]];
  563. nextPosition = nextPosition.join(' ');
  564. adjacentTried = (triedPositions[nextPosition] === true);
  565. module.debug('Trying adjacent strategy', nextPosition);
  566. }
  567. if(adjacentTried || oppositeTried) {
  568. module.debug('Using backup position', nextPosition);
  569. nextPosition = backup[position];
  570. }
  571. return nextPosition;
  572. }
  573. },
  574. set: {
  575. position: function(position, arrowOffset) {
  576. // exit conditions
  577. if($target.length === 0 || $popup.length === 0) {
  578. module.error(error.notFound);
  579. return;
  580. }
  581. var
  582. windowWidth = $(window).width(),
  583. windowHeight = $(window).height(),
  584. targetWidth = $target.outerWidth(),
  585. targetHeight = $target.outerHeight(),
  586. popupWidth = $popup.outerWidth(),
  587. popupHeight = $popup.outerHeight(),
  588. parentWidth = $offsetParent.outerWidth(),
  589. parentHeight = $offsetParent.outerHeight(),
  590. distanceAway = settings.distanceAway,
  591. targetElement = $target[0],
  592. marginTop = (settings.inline)
  593. ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10)
  594. : 0,
  595. marginLeft = (settings.inline)
  596. ? module.is.rtl()
  597. ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10)
  598. : parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left') , 10)
  599. : 0,
  600. target = (settings.inline || settings.popup)
  601. ? $target.position()
  602. : $target.offset(),
  603. computedPosition,
  604. positioning,
  605. offstagePosition
  606. ;
  607. position = position || $module.data(metadata.position) || settings.position;
  608. arrowOffset = arrowOffset || $module.data(metadata.offset) || settings.offset;
  609. if(target.top === 0 && target.left === 0) {
  610. module.debug('Popup target is hidden, no action taken');
  611. return false;
  612. }
  613. if(searchDepth == settings.maxSearchDepth && settings.lastResort) {
  614. module.debug('Using last resort position to display', settings.lastResort);
  615. position = settings.lastResort;
  616. }
  617. if(settings.inline) {
  618. module.debug('Adding targets margin to calculation');
  619. if(position == 'left center' || position == 'right center') {
  620. arrowOffset += marginTop;
  621. distanceAway += -marginLeft;
  622. }
  623. else if (position == 'top left' || position == 'top center' || position == 'top right') {
  624. arrowOffset += marginLeft;
  625. distanceAway -= marginTop;
  626. }
  627. else {
  628. arrowOffset += marginLeft;
  629. distanceAway += marginTop;
  630. }
  631. }
  632. module.debug('Calculating popup positioning', position);
  633. computedPosition = position;
  634. if (module.is.rtl()) {
  635. computedPosition = computedPosition.replace(/left|right/g, function (match) {
  636. return (match == 'left')
  637. ? 'right'
  638. : 'left'
  639. ;
  640. });
  641. module.debug('RTL: Popup positioning updated', computedPosition);
  642. }
  643. switch (computedPosition) {
  644. case 'top left':
  645. positioning = {
  646. top : 'auto',
  647. bottom : parentHeight - target.top + distanceAway,
  648. left : target.left + arrowOffset,
  649. right : 'auto'
  650. };
  651. break;
  652. case 'top center':
  653. positioning = {
  654. bottom : parentHeight - target.top + distanceAway,
  655. left : target.left + (targetWidth / 2) - (popupWidth / 2) + arrowOffset,
  656. top : 'auto',
  657. right : 'auto'
  658. };
  659. break;
  660. case 'top right':
  661. positioning = {
  662. bottom : parentHeight - target.top + distanceAway,
  663. right : parentWidth - target.left - targetWidth - arrowOffset,
  664. top : 'auto',
  665. left : 'auto'
  666. };
  667. break;
  668. case 'left center':
  669. positioning = {
  670. top : target.top + (targetHeight / 2) - (popupHeight / 2) + arrowOffset,
  671. right : parentWidth - target.left + distanceAway,
  672. left : 'auto',
  673. bottom : 'auto'
  674. };
  675. break;
  676. case 'right center':
  677. positioning = {
  678. top : target.top + (targetHeight / 2) - (popupHeight / 2) + arrowOffset,
  679. left : target.left + targetWidth + distanceAway,
  680. bottom : 'auto',
  681. right : 'auto'
  682. };
  683. break;
  684. case 'bottom left':
  685. positioning = {
  686. top : target.top + targetHeight + distanceAway,
  687. left : target.left + arrowOffset,
  688. bottom : 'auto',
  689. right : 'auto'
  690. };
  691. break;
  692. case 'bottom center':
  693. positioning = {
  694. top : target.top + targetHeight + distanceAway,
  695. left : target.left + (targetWidth / 2) - (popupWidth / 2) + arrowOffset,
  696. bottom : 'auto',
  697. right : 'auto'
  698. };
  699. break;
  700. case 'bottom right':
  701. positioning = {
  702. top : target.top + targetHeight + distanceAway,
  703. right : parentWidth - target.left - targetWidth - arrowOffset,
  704. left : 'auto',
  705. bottom : 'auto'
  706. };
  707. break;
  708. }
  709. if(positioning === undefined) {
  710. module.error(error.invalidPosition, position);
  711. }
  712. module.debug('Calculated popup positioning values', positioning);
  713. // tentatively place on stage
  714. $popup
  715. .css(positioning)
  716. .removeClass(className.position)
  717. .addClass(position)
  718. .addClass(className.loading)
  719. ;
  720. // check if is offstage
  721. offstagePosition = module.get.offstagePosition(position);
  722. // recursively find new positioning
  723. if(offstagePosition) {
  724. module.debug('Popup cant fit into viewport', position, offstagePosition);
  725. if(searchDepth < settings.maxSearchDepth) {
  726. searchDepth++;
  727. position = module.get.nextPosition(position);
  728. module.debug('Trying new position', position);
  729. return ($popup)
  730. ? module.set.position(position)
  731. : false
  732. ;
  733. }
  734. else if(!settings.lastResort) {
  735. module.debug('Popup could not find a position in view', $popup);
  736. // module.error(error.cannotPlace, element);
  737. module.remove.attempts();
  738. module.remove.loading();
  739. module.reset();
  740. return false;
  741. }
  742. }
  743. module.debug('Position is on stage', position);
  744. module.remove.attempts();
  745. module.set.fluidWidth();
  746. module.remove.loading();
  747. return true;
  748. },
  749. fluidWidth: function() {
  750. if( settings.setFluidWidth && $popup.hasClass(className.fluid) ) {
  751. $popup.css('width', $offsetParent.width());
  752. }
  753. },
  754. visible: function() {
  755. $module.addClass(className.visible);
  756. }
  757. },
  758. remove: {
  759. loading: function() {
  760. $popup.removeClass(className.loading);
  761. },
  762. visible: function() {
  763. $module.removeClass(className.visible);
  764. },
  765. attempts: function() {
  766. module.verbose('Resetting all searched positions');
  767. searchDepth = 0;
  768. triedPositions = false;
  769. }
  770. },
  771. bind: {
  772. events: function() {
  773. module.debug('Binding popup events to module');
  774. if(settings.on == 'click') {
  775. $module
  776. .on('click' + eventNamespace, module.toggle)
  777. ;
  778. }
  779. else if( module.get.startEvent() ) {
  780. $module
  781. .on(module.get.startEvent() + eventNamespace, module.event.start)
  782. .on(module.get.endEvent() + eventNamespace, module.event.end)
  783. ;
  784. }
  785. if(settings.target) {
  786. module.debug('Target set to element', $target);
  787. }
  788. $window.on('resize' + elementNamespace, module.event.resize);
  789. },
  790. popup: function() {
  791. module.verbose('Allowing hover events on popup to prevent closing');
  792. if( $popup && module.has.popup() ) {
  793. $popup
  794. .on('mouseenter' + eventNamespace, module.event.start)
  795. .on('mouseleave' + eventNamespace, module.event.end)
  796. ;
  797. }
  798. },
  799. close:function() {
  800. if(settings.hideOnScroll === true || settings.hideOnScroll == 'auto' && settings.on != 'click') {
  801. $document
  802. .one(module.get.scrollEvent() + elementNamespace, module.hideGracefully)
  803. ;
  804. $context
  805. .one(module.get.scrollEvent() + elementNamespace, module.hideGracefully)
  806. ;
  807. }
  808. if(settings.on == 'click' && settings.closable) {
  809. module.verbose('Binding popup close event to document');
  810. $document
  811. .on('click' + elementNamespace, function(event) {
  812. module.verbose('Pop-up clickaway intent detected');
  813. module.hideGracefully.call(element, event);
  814. })
  815. ;
  816. }
  817. }
  818. },
  819. unbind: {
  820. close: function() {
  821. if(settings.hideOnScroll === true || settings.hideOnScroll == 'auto' && settings.on != 'click') {
  822. $document
  823. .off('scroll' + elementNamespace, module.hide)
  824. ;
  825. $context
  826. .off('scroll' + elementNamespace, module.hide)
  827. ;
  828. }
  829. if(settings.on == 'click' && settings.closable) {
  830. module.verbose('Removing close event from document');
  831. $document
  832. .off('click' + elementNamespace)
  833. ;
  834. }
  835. }
  836. },
  837. has: {
  838. popup: function() {
  839. return ($popup && $popup.length > 0);
  840. }
  841. },
  842. is: {
  843. active: function() {
  844. return $module.hasClass(className.active);
  845. },
  846. animating: function() {
  847. return ( $popup && $popup.hasClass(className.animating) );
  848. },
  849. visible: function() {
  850. return $popup && $popup.hasClass(className.visible);
  851. },
  852. dropdown: function() {
  853. return $module.hasClass(className.dropdown);
  854. },
  855. hidden: function() {
  856. return !module.is.visible();
  857. },
  858. rtl: function () {
  859. return $module.css('direction') == 'rtl';
  860. }
  861. },
  862. reset: function() {
  863. module.remove.visible();
  864. if(settings.preserve) {
  865. if($.fn.transition !== undefined) {
  866. $popup
  867. .transition('remove transition')
  868. ;
  869. }
  870. }
  871. else {
  872. module.removePopup();
  873. }
  874. },
  875. setting: function(name, value) {
  876. if( $.isPlainObject(name) ) {
  877. $.extend(true, settings, name);
  878. }
  879. else if(value !== undefined) {
  880. settings[name] = value;
  881. }
  882. else {
  883. return settings[name];
  884. }
  885. },
  886. internal: function(name, value) {
  887. if( $.isPlainObject(name) ) {
  888. $.extend(true, module, name);
  889. }
  890. else if(value !== undefined) {
  891. module[name] = value;
  892. }
  893. else {
  894. return module[name];
  895. }
  896. },
  897. debug: function() {
  898. if(settings.debug) {
  899. if(settings.performance) {
  900. module.performance.log(arguments);
  901. }
  902. else {
  903. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  904. module.debug.apply(console, arguments);
  905. }
  906. }
  907. },
  908. verbose: function() {
  909. if(settings.verbose && settings.debug) {
  910. if(settings.performance) {
  911. module.performance.log(arguments);
  912. }
  913. else {
  914. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  915. module.verbose.apply(console, arguments);
  916. }
  917. }
  918. },
  919. error: function() {
  920. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  921. module.error.apply(console, arguments);
  922. },
  923. performance: {
  924. log: function(message) {
  925. var
  926. currentTime,
  927. executionTime,
  928. previousTime
  929. ;
  930. if(settings.performance) {
  931. currentTime = new Date().getTime();
  932. previousTime = time || currentTime;
  933. executionTime = currentTime - previousTime;
  934. time = currentTime;
  935. performance.push({
  936. 'Name' : message[0],
  937. 'Arguments' : [].slice.call(message, 1) || '',
  938. 'Element' : element,
  939. 'Execution Time' : executionTime
  940. });
  941. }
  942. clearTimeout(module.performance.timer);
  943. module.performance.timer = setTimeout(module.performance.display, 500);
  944. },
  945. display: function() {
  946. var
  947. title = settings.name + ':',
  948. totalTime = 0
  949. ;
  950. time = false;
  951. clearTimeout(module.performance.timer);
  952. $.each(performance, function(index, data) {
  953. totalTime += data['Execution Time'];
  954. });
  955. title += ' ' + totalTime + 'ms';
  956. if(moduleSelector) {
  957. title += ' \'' + moduleSelector + '\'';
  958. }
  959. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  960. console.groupCollapsed(title);
  961. if(console.table) {
  962. console.table(performance);
  963. }
  964. else {
  965. $.each(performance, function(index, data) {
  966. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  967. });
  968. }
  969. console.groupEnd();
  970. }
  971. performance = [];
  972. }
  973. },
  974. invoke: function(query, passedArguments, context) {
  975. var
  976. object = instance,
  977. maxDepth,
  978. found,
  979. response
  980. ;
  981. passedArguments = passedArguments || queryArguments;
  982. context = element || context;
  983. if(typeof query == 'string' && object !== undefined) {
  984. query = query.split(/[\. ]/);
  985. maxDepth = query.length - 1;
  986. $.each(query, function(depth, value) {
  987. var camelCaseValue = (depth != maxDepth)
  988. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  989. : query
  990. ;
  991. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  992. object = object[camelCaseValue];
  993. }
  994. else if( object[camelCaseValue] !== undefined ) {
  995. found = object[camelCaseValue];
  996. return false;
  997. }
  998. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  999. object = object[value];
  1000. }
  1001. else if( object[value] !== undefined ) {
  1002. found = object[value];
  1003. return false;
  1004. }
  1005. else {
  1006. return false;
  1007. }
  1008. });
  1009. }
  1010. if ( $.isFunction( found ) ) {
  1011. response = found.apply(context, passedArguments);
  1012. }
  1013. else if(found !== undefined) {
  1014. response = found;
  1015. }
  1016. if($.isArray(returnedValue)) {
  1017. returnedValue.push(response);
  1018. }
  1019. else if(returnedValue !== undefined) {
  1020. returnedValue = [returnedValue, response];
  1021. }
  1022. else if(response !== undefined) {
  1023. returnedValue = response;
  1024. }
  1025. return found;
  1026. }
  1027. };
  1028. if(methodInvoked) {
  1029. if(instance === undefined) {
  1030. module.initialize();
  1031. }
  1032. module.invoke(query);
  1033. }
  1034. else {
  1035. if(instance !== undefined) {
  1036. instance.invoke('destroy');
  1037. }
  1038. module.initialize();
  1039. }
  1040. })
  1041. ;
  1042. return (returnedValue !== undefined)
  1043. ? returnedValue
  1044. : this
  1045. ;
  1046. };
  1047. $.fn.popup.settings = {
  1048. name : 'Popup',
  1049. // module settings
  1050. debug : false,
  1051. verbose : false,
  1052. performance : true,
  1053. namespace : 'popup',
  1054. // callback only when element added to dom
  1055. onCreate : function(){},
  1056. // callback before element removed from dom
  1057. onRemove : function(){},
  1058. // callback before show animation
  1059. onShow : function(){},
  1060. // callback after show animation
  1061. onVisible : function(){},
  1062. // callback before hide animation
  1063. onHide : function(){},
  1064. // callback after hide animation
  1065. onHidden : function(){},
  1066. // when to show popup
  1067. on : 'hover',
  1068. // whether to add touchstart events when using hover
  1069. addTouchEvents : false,
  1070. // default position relative to element
  1071. position : 'top left',
  1072. // name of variation to use
  1073. variation : '',
  1074. // whether popup should be moved to context
  1075. movePopup : true,
  1076. // element which popup should be relative to
  1077. target : false,
  1078. // jq selector or element that should be used as popup
  1079. popup : false,
  1080. // popup should remain inline next to activator
  1081. inline : false,
  1082. // popup should be removed from page on hide
  1083. preserve : false,
  1084. // popup should not close when being hovered on
  1085. hoverable : false,
  1086. // explicitly set content
  1087. content : false,
  1088. // explicitly set html
  1089. html : false,
  1090. // explicitly set title
  1091. title : false,
  1092. // whether automatically close on clickaway when on click
  1093. closable : true,
  1094. // automatically hide on scroll
  1095. hideOnScroll : 'auto',
  1096. // hide other popups on show
  1097. exclusive : false,
  1098. // context to attach popups
  1099. context : 'body',
  1100. // position to prefer when calculating new position
  1101. prefer : 'opposite',
  1102. // specify position to appear even if it doesn't fit
  1103. lastResort : false,
  1104. // delay used to prevent accidental refiring of animations due to user error
  1105. delay : {
  1106. show : 50,
  1107. hide : 70
  1108. },
  1109. // whether fluid variation should assign width explicitly
  1110. setFluidWidth : true,
  1111. // transition settings
  1112. duration : 200,
  1113. transition : 'scale',
  1114. // distance away from activating element in px
  1115. distanceAway : 0,
  1116. // offset on aligning axis from calculated position
  1117. offset : 0,
  1118. // maximum times to look for a position before failing
  1119. maxSearchDepth : 20,
  1120. error: {
  1121. invalidPosition : 'The position you specified is not a valid position',
  1122. cannotPlace : 'No visible position could be found for the popup',
  1123. method : 'The method you called is not defined.',
  1124. noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>',
  1125. notFound : 'The target or popup you specified does not exist on the page'
  1126. },
  1127. metadata: {
  1128. activator : 'activator',
  1129. content : 'content',
  1130. html : 'html',
  1131. offset : 'offset',
  1132. position : 'position',
  1133. title : 'title',
  1134. variation : 'variation'
  1135. },
  1136. className : {
  1137. active : 'active',
  1138. animating : 'animating',
  1139. dropdown : 'dropdown',
  1140. fluid : 'fluid',
  1141. loading : 'loading',
  1142. popup : 'ui popup',
  1143. position : 'top left center bottom right',
  1144. visible : 'visible'
  1145. },
  1146. selector : {
  1147. popup : '.ui.popup'
  1148. },
  1149. templates: {
  1150. escape: function(string) {
  1151. var
  1152. badChars = /[&<>"'`]/g,
  1153. shouldEscape = /[&<>"'`]/,
  1154. escape = {
  1155. "&": "&amp;",
  1156. "<": "&lt;",
  1157. ">": "&gt;",
  1158. '"': "&quot;",
  1159. "'": "&#x27;",
  1160. "`": "&#x60;"
  1161. },
  1162. escapedChar = function(chr) {
  1163. return escape[chr];
  1164. }
  1165. ;
  1166. if(shouldEscape.test(string)) {
  1167. return string.replace(badChars, escapedChar);
  1168. }
  1169. return string;
  1170. },
  1171. popup: function(text) {
  1172. var
  1173. html = '',
  1174. escape = $.fn.popup.settings.templates.escape
  1175. ;
  1176. if(typeof text !== undefined) {
  1177. if(typeof text.title !== undefined && text.title) {
  1178. text.title = escape(text.title);
  1179. html += '<div class="header">' + text.title + '</div>';
  1180. }
  1181. if(typeof text.content !== undefined && text.content) {
  1182. text.content = escape(text.content);
  1183. html += '<div class="content">' + text.content + '</div>';
  1184. }
  1185. }
  1186. return html;
  1187. }
  1188. }
  1189. };
  1190. // Adds easing
  1191. $.extend( $.easing, {
  1192. easeOutQuad: function (x, t, b, c, d) {
  1193. return -c *(t/=d)*(t-2) + b;
  1194. }
  1195. });
  1196. })( jQuery, window , document );