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.

1468 lines
47 KiB

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