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.

1387 lines
44 KiB

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