solidus_backend 1.1.4 → 1.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of solidus_backend might be problematic. Click here for more details.

Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/solidus-style-guide-logo.png +0 -0
  3. data/app/assets/javascripts/spree/backend/admin.js.erb +1 -7
  4. data/app/assets/javascripts/spree/backend/checkouts/edit.js +2 -2
  5. data/app/assets/javascripts/spree/backend/navigation.coffee +10 -0
  6. data/app/assets/javascripts/spree/backend/option_type_autocomplete.js.erb +6 -6
  7. data/app/assets/javascripts/spree/backend/option_value_picker.js +6 -6
  8. data/app/assets/javascripts/spree/backend/product_picker.js +6 -6
  9. data/app/assets/javascripts/spree/backend/shipments.js.erb +17 -3
  10. data/app/assets/javascripts/spree/backend/stock_movement.js.coffee +2 -2
  11. data/app/assets/javascripts/spree/backend/style_guide.coffee +2 -0
  12. data/app/assets/javascripts/spree/backend/taxon_autocomplete.js.erb +6 -6
  13. data/app/assets/javascripts/spree/backend/taxonomy.js.coffee +82 -109
  14. data/app/assets/javascripts/spree/backend/taxons.js.coffee +4 -5
  15. data/app/assets/javascripts/spree/backend/user_picker.js +8 -8
  16. data/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee.erb +3 -3
  17. data/app/assets/javascripts/spree/backend.js +3 -6
  18. data/app/assets/stylesheets/spree/backend/components/_messages.scss +1 -4
  19. data/app/assets/stylesheets/spree/backend/components/_navigation.scss +99 -115
  20. data/app/assets/stylesheets/spree/backend/components/_progress.scss +1 -1
  21. data/app/assets/stylesheets/spree/backend/components/_sidebar.scss +6 -0
  22. data/app/assets/stylesheets/spree/backend/globals/_variables.scss +18 -0
  23. data/app/assets/stylesheets/spree/backend/globals/mixins/_caret.scss +35 -0
  24. data/app/assets/stylesheets/spree/backend/plugins/_powertip.scss +1 -0
  25. data/app/assets/stylesheets/spree/backend/sections/_style_guide.scss +132 -0
  26. data/app/assets/stylesheets/spree/backend/sections/_taxonomies.scss +60 -0
  27. data/app/assets/stylesheets/spree/backend/shared/_forms.scss +2 -0
  28. data/app/assets/stylesheets/spree/backend/shared/_layout.scss +36 -4
  29. data/app/assets/stylesheets/spree/backend/shared/_typography.scss +3 -6
  30. data/app/assets/stylesheets/spree/backend/spree_admin.scss +4 -1
  31. data/app/assets/stylesheets/spree/backend.css +1 -1
  32. data/app/controllers/spree/admin/orders_controller.rb +6 -6
  33. data/app/controllers/spree/admin/payment_methods_controller.rb +1 -1
  34. data/app/controllers/spree/admin/payments_controller.rb +1 -1
  35. data/app/controllers/spree/admin/products_controller.rb +0 -1
  36. data/app/controllers/spree/admin/reports_controller.rb +2 -2
  37. data/app/controllers/spree/admin/search_controller.rb +9 -7
  38. data/app/controllers/spree/admin/stock_transfers_controller.rb +6 -14
  39. data/app/controllers/spree/admin/style_guide_controller.rb +33 -0
  40. data/app/helpers/spree/admin/adjustments_helper.rb +2 -6
  41. data/app/helpers/spree/admin/base_helper.rb +7 -6
  42. data/app/helpers/spree/admin/navigation_helper.rb +12 -5
  43. data/app/helpers/spree/admin/orders_helper.rb +1 -1
  44. data/app/helpers/spree/admin/products_helper.rb +2 -4
  45. data/app/helpers/spree/admin/stock_movements_helper.rb +3 -3
  46. data/app/views/spree/admin/adjustments/_adjustments_table.html.erb +2 -2
  47. data/app/views/spree/admin/customer_returns/_reimbursements_table.html.erb +1 -1
  48. data/app/views/spree/admin/customer_returns/_return_item_selection.html.erb +1 -1
  49. data/app/views/spree/admin/customer_returns/edit.html.erb +1 -1
  50. data/app/views/spree/admin/images/edit.html.erb +1 -3
  51. data/app/views/spree/admin/images/index.html.erb +0 -2
  52. data/app/views/spree/admin/option_types/edit.html.erb +2 -4
  53. data/app/views/spree/admin/option_types/index.html.erb +0 -2
  54. data/app/views/spree/admin/option_types/new.html.erb +0 -2
  55. data/app/views/spree/admin/orders/_shipment_manifest.html.erb +1 -1
  56. data/app/views/spree/admin/orders/index.html.erb +1 -1
  57. data/app/views/spree/admin/product_properties/index.html.erb +0 -1
  58. data/app/views/spree/admin/products/edit.html.erb +0 -2
  59. data/app/views/spree/admin/products/index.html.erb +2 -4
  60. data/app/views/spree/admin/products/new.html.erb +0 -2
  61. data/app/views/spree/admin/promotion_categories/edit.html.erb +0 -2
  62. data/app/views/spree/admin/promotion_categories/index.html.erb +0 -2
  63. data/app/views/spree/admin/promotion_categories/new.html.erb +0 -2
  64. data/app/views/spree/admin/promotions/actions/_create_adjustment.html.erb +2 -28
  65. data/app/views/spree/admin/promotions/actions/_create_item_adjustments.html.erb +2 -26
  66. data/app/views/spree/admin/promotions/actions/_promotion_calculators_with_custom_fields.html.erb +28 -0
  67. data/app/views/spree/admin/promotions/edit.html.erb +0 -2
  68. data/app/views/spree/admin/promotions/index.html.erb +0 -2
  69. data/app/views/spree/admin/promotions/new.html.erb +0 -2
  70. data/app/views/spree/admin/properties/edit.html.erb +0 -2
  71. data/app/views/spree/admin/properties/index.html.erb +2 -4
  72. data/app/views/spree/admin/properties/new.html.erb +0 -2
  73. data/app/views/spree/admin/properties/new.js.erb +1 -1
  74. data/app/views/spree/admin/prototypes/edit.html.erb +0 -2
  75. data/app/views/spree/admin/prototypes/index.html.erb +0 -2
  76. data/app/views/spree/admin/prototypes/new.html.erb +0 -2
  77. data/app/views/spree/admin/reimbursements/edit.html.erb +2 -2
  78. data/app/views/spree/admin/reimbursements/show.html.erb +9 -9
  79. data/app/views/spree/admin/return_authorizations/_form.html.erb +16 -16
  80. data/app/views/spree/admin/search/users.rabl +1 -1
  81. data/app/views/spree/admin/shared/_head.html.erb +11 -0
  82. data/app/views/spree/admin/shared/_header.html.erb +4 -8
  83. data/app/views/spree/admin/shared/_menu.html.erb +4 -8
  84. data/app/views/spree/admin/shared/_navigation.html.erb +9 -0
  85. data/app/views/spree/admin/shared/_navigation_footer.html.erb +3 -0
  86. data/app/views/spree/admin/shared/_product_sub_menu.html.erb +20 -22
  87. data/app/views/spree/admin/shared/_promotion_sub_menu.html.erb +8 -10
  88. data/app/views/spree/admin/shared/_stock_sub_menu.html.erb +8 -10
  89. data/app/views/spree/admin/shared/_tabs.html.erb +9 -3
  90. data/app/views/spree/admin/shipping_methods/_form.html.erb +2 -2
  91. data/app/views/spree/admin/stock_items/index.html.erb +1 -2
  92. data/app/views/spree/admin/stock_transfers/edit.html.erb +0 -1
  93. data/app/views/spree/admin/stock_transfers/index.html.erb +0 -3
  94. data/app/views/spree/admin/stock_transfers/new.html.erb +0 -2
  95. data/app/views/spree/admin/stock_transfers/receive.html.erb +0 -2
  96. data/app/views/spree/admin/stock_transfers/show.html.erb +0 -2
  97. data/app/views/spree/admin/stock_transfers/tracking_info.html.erb +0 -2
  98. data/app/views/spree/admin/style_guide/index.html.erb +5 -0
  99. data/app/views/spree/admin/style_guide/topics/forms/_building_forms.html.erb +79 -0
  100. data/app/views/spree/admin/style_guide/topics/forms/_validation.html.erb +14 -0
  101. data/app/views/spree/admin/style_guide/topics/messaging/_flashes.html.erb +13 -0
  102. data/app/views/spree/admin/style_guide/topics/messaging/_loading.html.erb +20 -0
  103. data/app/views/spree/admin/style_guide/topics/messaging/_tooltips.html.erb +16 -0
  104. data/app/views/spree/admin/style_guide/topics/tables/_building_tables.html.erb +44 -0
  105. data/app/views/spree/admin/style_guide/topics/tables/_pagination.html.erb +11 -0
  106. data/app/views/spree/admin/style_guide/topics/typography/_colors.html.erb +64 -0
  107. data/app/views/spree/admin/style_guide/topics/typography/_fonts.html.erb +19 -0
  108. data/app/views/spree/admin/style_guide/topics/typography/_icons.html.erb +13 -0
  109. data/app/views/spree/admin/style_guide/topics/typography/_lists.html.erb +25 -0
  110. data/app/views/spree/admin/style_guide/topics/typography/_tags.html.erb +25 -0
  111. data/app/views/spree/admin/taxonomies/_js_head.html.erb +0 -0
  112. data/app/views/spree/admin/taxonomies/edit.erb +5 -6
  113. data/app/views/spree/admin/taxonomies/index.html.erb +0 -2
  114. data/app/views/spree/admin/taxonomies/new.html.erb +0 -2
  115. data/app/views/spree/admin/taxons/_form.html.erb +1 -1
  116. data/app/views/spree/admin/taxons/_list_template.html.erb +17 -0
  117. data/app/views/spree/admin/taxons/index.html.erb +0 -3
  118. data/app/views/spree/admin/users/orders.html.erb +1 -1
  119. data/app/views/spree/admin/variants/edit.html.erb +0 -2
  120. data/app/views/spree/admin/variants/index.html.erb +0 -2
  121. data/app/views/spree/layouts/admin.html.erb +3 -4
  122. data/app/views/spree/layouts/admin_style_guide.html.erb +58 -0
  123. data/config/routes.rb +4 -2
  124. data/lib/spree/backend/engine.rb +0 -1
  125. data/lib/spree/backend.rb +0 -3
  126. data/solidus_backend.gemspec +5 -7
  127. data/spec/controllers/spree/admin/missing_products_controller_spec.rb +1 -1
  128. data/spec/controllers/spree/admin/orders_controller_spec.rb +14 -14
  129. data/spec/controllers/spree/admin/payment_methods_controller_spec.rb +2 -4
  130. data/spec/controllers/spree/admin/payments_controller_spec.rb +2 -2
  131. data/spec/controllers/spree/admin/products_controller_spec.rb +3 -3
  132. data/spec/controllers/spree/admin/reports_controller_spec.rb +4 -4
  133. data/spec/controllers/spree/admin/return_authorizations_controller_spec.rb +1 -1
  134. data/spec/controllers/spree/admin/shipping_methods_controller_spec.rb +1 -1
  135. data/spec/controllers/spree/admin/stock_locations_controller_spec.rb +1 -1
  136. data/spec/controllers/spree/admin/stock_transfers_controller_spec.rb +13 -11
  137. data/spec/features/admin/configuration/general_settings_spec.rb +10 -7
  138. data/spec/features/admin/configuration/payment_methods_spec.rb +1 -1
  139. data/spec/features/admin/configuration/shipping_methods_spec.rb +1 -1
  140. data/spec/features/admin/configuration/states_spec.rb +1 -1
  141. data/spec/features/admin/configuration/tax_rates_spec.rb +6 -7
  142. data/spec/features/admin/configuration/taxonomies_spec.rb +3 -3
  143. data/spec/features/admin/homepage_spec.rb +6 -6
  144. data/spec/features/admin/orders/cancelling_and_resuming_spec.rb +4 -8
  145. data/spec/features/admin/orders/customer_details_spec.rb +9 -8
  146. data/spec/features/admin/orders/line_items_spec.rb +1 -1
  147. data/spec/features/admin/orders/listing_spec.rb +2 -2
  148. data/spec/features/admin/orders/log_entries_spec.rb +3 -3
  149. data/spec/features/admin/orders/new_order_spec.rb +3 -15
  150. data/spec/features/admin/orders/order_details_spec.rb +17 -20
  151. data/spec/features/admin/orders/payments_spec.rb +10 -10
  152. data/spec/features/admin/orders/shipments_spec.rb +2 -2
  153. data/spec/features/admin/products/edit/images_spec.rb +5 -3
  154. data/spec/features/admin/products/edit/products_spec.rb +11 -11
  155. data/spec/features/admin/products/edit/taxons_spec.rb +14 -38
  156. data/spec/features/admin/products/edit/variants_spec.rb +3 -3
  157. data/spec/features/admin/products/option_types_spec.rb +22 -21
  158. data/spec/features/admin/products/products_spec.rb +15 -12
  159. data/spec/features/admin/products/properties_spec.rb +3 -3
  160. data/spec/features/admin/products/prototypes_spec.rb +4 -4
  161. data/spec/features/admin/products/stock_management_spec.rb +5 -5
  162. data/spec/features/admin/products/variant_spec.rb +8 -8
  163. data/spec/features/admin/promotion_adjustments_spec.rb +6 -13
  164. data/spec/features/admin/promotions/option_value_rule_spec.rb +0 -20
  165. data/spec/features/admin/reports_spec.rb +2 -2
  166. data/spec/features/admin/stock_transfer_spec.rb +3 -3
  167. data/spec/features/admin/store_credits_spec.rb +2 -2
  168. data/spec/features/admin/users_spec.rb +6 -6
  169. data/spec/helpers/admin/navigation_helper_spec.rb +5 -0
  170. data/spec/helpers/admin/reimbursements_helper_spec.rb +1 -1
  171. data/spec/helpers/admin/store_credit_events_helper_spec.rb +1 -1
  172. data/spec/spec_helper.rb +3 -1
  173. data/spec/support/feature/base_feature_helper.rb +10 -0
  174. data/vendor/assets/images/jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  175. data/vendor/assets/images/jquery-ui/ui-bg_flat_0_eeeeee_40x100.png +0 -0
  176. data/vendor/assets/images/jquery-ui/ui-bg_flat_55_ffffff_40x100.png +0 -0
  177. data/vendor/assets/images/jquery-ui/ui-bg_flat_75_ffffff_40x100.png +0 -0
  178. data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  179. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_f6f6f6_1x100.png +0 -0
  180. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_25_0073ea_1x100.png +0 -0
  181. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_50_dddddd_1x100.png +0 -0
  182. data/vendor/assets/images/jquery-ui/ui-icons_0073ea_256x240.png +0 -0
  183. data/vendor/assets/images/jquery-ui/ui-icons_454545_256x240.png +0 -0
  184. data/vendor/assets/images/jquery-ui/ui-icons_666666_256x240.png +0 -0
  185. data/vendor/assets/images/jquery-ui/ui-icons_ff0084_256x240.png +0 -0
  186. data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
  187. data/vendor/assets/javascripts/jquery.powertip.js +792 -423
  188. data/vendor/assets/javascripts/jquery.sticky-kit.min.js +10 -0
  189. data/vendor/assets/javascripts/prism.js +7 -0
  190. data/vendor/assets/stylesheets/prism.css +139 -0
  191. metadata +44 -51
  192. data/app/assets/stylesheets/spree/backend/plugins/_jstree.scss +0 -135
  193. data/app/views/spree/admin/shared/_sub_menu.html.erb +0 -9
  194. data/spec/features/admin/promotions/user_rule_spec.rb +0 -25
  195. data/vendor/assets/javascripts/equalize.js +0 -41
  196. data/vendor/assets/javascripts/jquery.jstree/jquery.jstree.js +0 -4540
  197. data/vendor/assets/javascripts/jquery.jstree/themes/apple/bg.jpg +0 -0
  198. data/vendor/assets/javascripts/jquery.jstree/themes/apple/d.png +0 -0
  199. data/vendor/assets/javascripts/jquery.jstree/themes/apple/dot_for_ie.gif +0 -0
  200. data/vendor/assets/javascripts/jquery.jstree/themes/apple/style.scss +0 -61
  201. data/vendor/assets/javascripts/jquery.jstree/themes/apple/throbber.gif +0 -0
  202. data/vendor/assets/javascripts/jquery.vAlign.js +0 -11
  203. data/vendor/assets/javascripts/modernizr.js +0 -4
  204. data/vendor/assets/javascripts/responsive-tables.js +0 -42
  205. data/vendor/assets/stylesheets/responsive-tables.css +0 -21
@@ -1,119 +1,158 @@
1
- /**
2
- * PowerTip
3
- *
4
- * @fileoverview jQuery plugin that creates hover tooltips.
5
- * @link http://stevenbenner.github.com/jquery-powertip/
6
- * @author Steven Benner (http://stevenbenner.com/)
7
- * @version 1.1.0
8
- * @requires jQuery 1.7+
9
- *
10
- * @license jQuery PowerTip Plugin v1.1.0
11
- * http://stevenbenner.github.com/jquery-powertip/
12
- * Copyright 2012 Steven Benner (http://stevenbenner.com/)
13
- * Released under the MIT license.
14
- * <https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt>
15
- */
16
-
17
- (function($) {
18
- 'use strict';
19
-
1
+ /*!
2
+ PowerTip - v1.2.0 - 2013-04-03
3
+ http://stevenbenner.github.com/jquery-powertip/
4
+ Copyright (c) 2013 Steven Benner (http://stevenbenner.com/).
5
+ Released under MIT license.
6
+ https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt
7
+ */
8
+ (function(factory) {
9
+ if (typeof define === 'function' && define.amd) {
10
+ // AMD. Register as an anonymous module.
11
+ define(['jquery'], factory);
12
+ } else {
13
+ // Browser globals
14
+ factory(jQuery);
15
+ }
16
+ }(function($) {
20
17
  // useful private variables
21
18
  var $document = $(document),
22
19
  $window = $(window),
23
20
  $body = $('body');
24
21
 
22
+ // constants
23
+ var DATA_DISPLAYCONTROLLER = 'displayController',
24
+ DATA_HASACTIVEHOVER = 'hasActiveHover',
25
+ DATA_FORCEDOPEN = 'forcedOpen',
26
+ DATA_HASMOUSEMOVE = 'hasMouseMove',
27
+ DATA_MOUSEONTOTIP = 'mouseOnToPopup',
28
+ DATA_ORIGINALTITLE = 'originalTitle',
29
+ DATA_POWERTIP = 'powertip',
30
+ DATA_POWERTIPJQ = 'powertipjq',
31
+ DATA_POWERTIPTARGET = 'powertiptarget',
32
+ RAD2DEG = 180 / Math.PI;
33
+
25
34
  /**
26
35
  * Session data
27
36
  * Private properties global to all powerTip instances
28
- * @type Object
29
37
  */
30
38
  var session = {
31
- isPopOpen: false,
32
- isFixedPopOpen: false,
39
+ isTipOpen: false,
40
+ isFixedTipOpen: false,
33
41
  isClosing: false,
34
- popOpenImminent: false,
42
+ tipOpenImminent: false,
35
43
  activeHover: null,
36
44
  currentX: 0,
37
45
  currentY: 0,
38
46
  previousX: 0,
39
47
  previousY: 0,
40
48
  desyncTimeout: null,
41
- mouseTrackingActive: false
49
+ mouseTrackingActive: false,
50
+ delayInProgress: false,
51
+ windowWidth: 0,
52
+ windowHeight: 0,
53
+ scrollTop: 0,
54
+ scrollLeft: 0
42
55
  };
43
56
 
44
57
  /**
45
- * Display hover tooltips on the matched elements.
46
- * @param {Object} opts The options object to use for the plugin.
47
- * @return {Object} jQuery object for the matched selectors.
58
+ * Collision enumeration
59
+ * @enum {number}
48
60
  */
49
- $.fn.powerTip = function(opts) {
61
+ var Collision = {
62
+ none: 0,
63
+ top: 1,
64
+ bottom: 2,
65
+ left: 4,
66
+ right: 8
67
+ };
50
68
 
69
+ /**
70
+ * Display hover tooltips on the matched elements.
71
+ * @param {(Object|string)} opts The options object to use for the plugin, or
72
+ * the name of a method to invoke on the first matched element.
73
+ * @param {*=} [arg] Argument for an invoked method (optional).
74
+ * @return {jQuery} jQuery object for the matched selectors.
75
+ */
76
+ $.fn.powerTip = function(opts, arg) {
51
77
  // don't do any work if there were no matched elements
52
78
  if (!this.length) {
53
79
  return this;
54
80
  }
55
81
 
56
- // extend options
82
+ // handle api method calls on the plugin, e.g. powerTip('hide')
83
+ if ($.type(opts) === 'string' && $.powerTip[opts]) {
84
+ return $.powerTip[opts].call(this, this, arg);
85
+ }
86
+
87
+ // extend options and instantiate TooltipController
57
88
  var options = $.extend({}, $.fn.powerTip.defaults, opts),
58
89
  tipController = new TooltipController(options);
59
90
 
60
- // hook mouse tracking
61
- initMouseTracking();
91
+ // hook mouse and viewport dimension tracking
92
+ initTracking();
62
93
 
63
94
  // setup the elements
64
- this.each(function() {
95
+ this.each(function elementSetup() {
65
96
  var $this = $(this),
66
- dataPowertip = $this.data('powertip'),
67
- dataElem = $this.data('powertipjq'),
68
- dataTarget = $this.data('powertiptarget'),
69
- title = $this.attr('title');
70
-
97
+ dataPowertip = $this.data(DATA_POWERTIP),
98
+ dataElem = $this.data(DATA_POWERTIPJQ),
99
+ dataTarget = $this.data(DATA_POWERTIPTARGET),
100
+ title;
101
+
102
+ // handle repeated powerTip calls on the same element by destroying the
103
+ // original instance hooked to it and replacing it with this call
104
+ if ($this.data(DATA_DISPLAYCONTROLLER)) {
105
+ $.powerTip.destroy($this);
106
+ }
71
107
 
72
108
  // attempt to use title attribute text if there is no data-powertip,
73
109
  // data-powertipjq or data-powertiptarget. If we do use the title
74
110
  // attribute, delete the attribute so the browser will not show it
111
+ title = $this.attr('title');
75
112
  if (!dataPowertip && !dataTarget && !dataElem && title) {
76
- $this.data('powertip', title);
113
+ $this.data(DATA_POWERTIP, title);
114
+ $this.data(DATA_ORIGINALTITLE, title);
77
115
  $this.removeAttr('title');
78
116
  }
79
117
 
80
118
  // create hover controllers for each element
81
119
  $this.data(
82
- 'displayController',
120
+ DATA_DISPLAYCONTROLLER,
83
121
  new DisplayController($this, options, tipController)
84
122
  );
85
123
  });
86
124
 
87
- // attach hover events to all matched elements
88
- return this.on({
89
- // mouse events
90
- mouseenter: function(event) {
91
- trackMouse(event);
92
- session.previousX = event.pageX;
93
- session.previousY = event.pageY;
94
- $(this).data('displayController').show();
95
- },
96
- mouseleave: function() {
97
- $(this).data('displayController').hide();
98
- },
99
-
100
- // keyboard events
101
- focus: function() {
102
- var element = $(this);
103
- if (!isMouseOver(element)) {
104
- element.data('displayController').show(true);
125
+ // attach events to matched elements if the manual options is not enabled
126
+ if (!options.manual) {
127
+ this.on({
128
+ // mouse events
129
+ 'mouseenter.powertip': function elementMouseEnter(event) {
130
+ $.powerTip.show(this, event);
131
+ },
132
+ 'mouseleave.powertip': function elementMouseLeave() {
133
+ $.powerTip.hide(this);
134
+ },
135
+ // keyboard events
136
+ 'focus.powertip': function elementFocus() {
137
+ $.powerTip.show(this);
138
+ },
139
+ 'blur.powertip': function elementBlur() {
140
+ $.powerTip.hide(this, true);
141
+ },
142
+ 'keydown.powertip': function elementKeyDown(event) {
143
+ // close tooltip when the escape key is pressed
144
+ if (event.keyCode === 27) {
145
+ $.powerTip.hide(this, true);
146
+ }
105
147
  }
106
- },
107
- blur: function() {
108
- $(this).data('displayController').hide(true);
109
- }
110
- });
148
+ });
149
+ }
111
150
 
151
+ return this;
112
152
  };
113
153
 
114
154
  /**
115
155
  * Default options for the powerTip plugin.
116
- * @type Object
117
156
  */
118
157
  $.fn.powerTip.defaults = {
119
158
  fadeInTime: 200,
@@ -126,15 +165,15 @@
126
165
  placement: 'n',
127
166
  smartPlacement: false,
128
167
  offset: 10,
129
- mouseOnToPopup: false
168
+ mouseOnToPopup: false,
169
+ manual: false
130
170
  };
131
171
 
132
172
  /**
133
173
  * Default smart placement priority lists.
134
- * The first item in the array is the highest priority, the last is the
135
- * lowest. The last item is also the default, which will be used if all
136
- * previous options do not fit.
137
- * @type Object
174
+ * The first item in the array is the highest priority, the last is the lowest.
175
+ * The last item is also the default, which will be used if all previous options
176
+ * do not fit.
138
177
  */
139
178
  $.fn.powerTip.smartPlacementLists = {
140
179
  n: ['n', 'ne', 'nw', 's'],
@@ -144,47 +183,125 @@
144
183
  nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
145
184
  ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
146
185
  sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
147
- se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se']
186
+ se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
187
+ 'nw-alt': ['nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e'],
188
+ 'ne-alt': ['ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w'],
189
+ 'sw-alt': ['sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e'],
190
+ 'se-alt': ['se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w']
148
191
  };
149
192
 
150
193
  /**
151
194
  * Public API
152
- * @type Object
153
195
  */
154
196
  $.powerTip = {
155
-
156
197
  /**
157
198
  * Attempts to show the tooltip for the specified element.
158
- * @public
159
- * @param {Object} element The element that the tooltip should for.
199
+ * @param {jQuery|Element} element The element to open the tooltip for.
200
+ * @param {jQuery.Event=} event jQuery event for hover intent and mouse
201
+ * tracking (optional).
160
202
  */
161
- showTip: function(element) {
162
- // close any open tooltip
163
- $.powerTip.closeTip();
164
- // grab only the first matched element and ask it to show its tip
165
- element = element.first();
166
- if (!isMouseOver(element)) {
167
- element.data('displayController').show(true, true);
203
+ show: function apiShowTip(element, event) {
204
+ if (event) {
205
+ trackMouse(event);
206
+ session.previousX = event.pageX;
207
+ session.previousY = event.pageY;
208
+ $(element).data(DATA_DISPLAYCONTROLLER).show();
209
+ } else {
210
+ $(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true);
168
211
  }
212
+ return element;
213
+ },
214
+
215
+ /**
216
+ * Repositions the tooltip on the element.
217
+ * @param {jQuery|Element} element The element the tooltip is shown for.
218
+ */
219
+ reposition: function apiResetPosition(element) {
220
+ $(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();
221
+ return element;
169
222
  },
170
223
 
171
224
  /**
172
225
  * Attempts to close any open tooltips.
173
- * @public
226
+ * @param {(jQuery|Element)=} element The element with the tooltip that
227
+ * should be closed (optional).
228
+ * @param {boolean=} immediate Disable close delay (optional).
174
229
  */
175
- closeTip: function() {
176
- $document.triggerHandler('closePowerTip');
177
- }
230
+ hide: function apiCloseTip(element, immediate) {
231
+ if (element) {
232
+ $(element).first().data(DATA_DISPLAYCONTROLLER).hide(immediate);
233
+ } else {
234
+ if (session.activeHover) {
235
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true);
236
+ }
237
+ }
238
+ return element;
239
+ },
178
240
 
241
+ /**
242
+ * Destroy and roll back any powerTip() instance on the specified element.
243
+ * @param {jQuery|Element} element The element with the powerTip instance.
244
+ */
245
+ destroy: function apiDestroy(element) {
246
+ $(element).off('.powertip').each(function destroy() {
247
+ var $this = $(this),
248
+ dataAttributes = [
249
+ DATA_ORIGINALTITLE,
250
+ DATA_DISPLAYCONTROLLER,
251
+ DATA_HASACTIVEHOVER,
252
+ DATA_FORCEDOPEN
253
+ ];
254
+
255
+ if ($this.data(DATA_ORIGINALTITLE)) {
256
+ $this.attr('title', $this.data(DATA_ORIGINALTITLE));
257
+ dataAttributes.push(DATA_POWERTIP);
258
+ }
259
+
260
+ $this.removeData(dataAttributes);
261
+ });
262
+ return element;
263
+ }
179
264
  };
180
265
 
266
+ // API aliasing
267
+ $.powerTip.showTip = $.powerTip.show;
268
+ $.powerTip.closeTip = $.powerTip.hide;
269
+
270
+ /**
271
+ * Creates a new CSSCoordinates object.
272
+ * @private
273
+ * @constructor
274
+ */
275
+ function CSSCoordinates() {
276
+ var me = this;
277
+
278
+ // initialize object properties
279
+ me.top = 'auto';
280
+ me.left = 'auto';
281
+ me.right = 'auto';
282
+ me.bottom = 'auto';
283
+
284
+ /**
285
+ * Set a property to a value.
286
+ * @private
287
+ * @param {string} property The name of the property.
288
+ * @param {number} value The value of the property.
289
+ */
290
+ me.set = function(property, value) {
291
+ if ($.isNumeric(value)) {
292
+ me[property] = Math.round(value);
293
+ }
294
+ };
295
+ }
296
+
181
297
  /**
182
298
  * Creates a new tooltip display controller.
183
299
  * @private
184
300
  * @constructor
185
- * @param {Object} element The element that this controller will handle.
301
+ * @param {jQuery} element The element that this controller will handle.
186
302
  * @param {Object} options Options object containing settings.
187
- * @param {TooltipController} tipController The TooltipController for this instance.
303
+ * @param {TooltipController} tipController The TooltipController object for
304
+ * this instance.
188
305
  */
189
306
  function DisplayController(element, options, tipController) {
190
307
  var hoverTimer = null;
@@ -192,24 +309,25 @@
192
309
  /**
193
310
  * Begins the process of showing a tooltip.
194
311
  * @private
195
- * @param {Boolean=} immediate Skip intent testing (optional).
196
- * @param {Boolean=} forceOpen Ignore cursor position and force tooltip to open (optional).
312
+ * @param {boolean=} immediate Skip intent testing (optional).
313
+ * @param {boolean=} forceOpen Ignore cursor position and force tooltip to
314
+ * open (optional).
197
315
  */
198
316
  function openTooltip(immediate, forceOpen) {
199
317
  cancelTimer();
200
- if (!element.data('hasActiveHover')) {
318
+ if (!element.data(DATA_HASACTIVEHOVER)) {
201
319
  if (!immediate) {
202
- session.popOpenImminent = true;
320
+ session.tipOpenImminent = true;
203
321
  hoverTimer = setTimeout(
204
- function() {
322
+ function intentDelay() {
205
323
  hoverTimer = null;
206
- checkForIntent(element);
324
+ checkForIntent();
207
325
  },
208
326
  options.intentPollInterval
209
327
  );
210
328
  } else {
211
329
  if (forceOpen) {
212
- element.data('forcedOpen', true);
330
+ element.data(DATA_FORCEDOPEN, true);
213
331
  }
214
332
  tipController.showTip(element);
215
333
  }
@@ -219,18 +337,20 @@
219
337
  /**
220
338
  * Begins the process of closing a tooltip.
221
339
  * @private
222
- * @param {Boolean=} disableDelay Disable close delay (optional).
340
+ * @param {boolean=} disableDelay Disable close delay (optional).
223
341
  */
224
342
  function closeTooltip(disableDelay) {
225
343
  cancelTimer();
226
- if (element.data('hasActiveHover')) {
227
- session.popOpenImminent = false;
228
- element.data('forcedOpen', false);
344
+ session.tipOpenImminent = false;
345
+ if (element.data(DATA_HASACTIVEHOVER)) {
346
+ element.data(DATA_FORCEDOPEN, false);
229
347
  if (!disableDelay) {
348
+ session.delayInProgress = true;
230
349
  hoverTimer = setTimeout(
231
- function() {
350
+ function closeDelay() {
232
351
  hoverTimer = null;
233
352
  tipController.hideTip(element);
353
+ session.delayInProgress = false;
234
354
  },
235
355
  options.closeDelay
236
356
  );
@@ -241,8 +361,8 @@
241
361
  }
242
362
 
243
363
  /**
244
- * Checks mouse position to make sure that the user intended to hover
245
- * on the specified element before showing the tooltip.
364
+ * Checks mouse position to make sure that the user intended to hover on the
365
+ * specified element before showing the tooltip.
246
366
  * @private
247
367
  */
248
368
  function checkForIntent() {
@@ -268,14 +388,238 @@
268
388
  */
269
389
  function cancelTimer() {
270
390
  hoverTimer = clearTimeout(hoverTimer);
391
+ session.delayInProgress = false;
392
+ }
393
+
394
+ /**
395
+ * Repositions the tooltip on this element.
396
+ * @private
397
+ */
398
+ function repositionTooltip() {
399
+ tipController.resetPosition(element);
271
400
  }
272
401
 
273
402
  // expose the methods
274
- return {
275
- show: openTooltip,
276
- hide: closeTooltip,
277
- cancel: cancelTimer
278
- };
403
+ this.show = openTooltip;
404
+ this.hide = closeTooltip;
405
+ this.cancel = cancelTimer;
406
+ this.resetPosition = repositionTooltip;
407
+ }
408
+
409
+ /**
410
+ * Creates a new Placement Calculator.
411
+ * @private
412
+ * @constructor
413
+ */
414
+ function PlacementCalculator() {
415
+ /**
416
+ * Compute the CSS position to display a tooltip at the specified placement
417
+ * relative to the specified element.
418
+ * @private
419
+ * @param {jQuery} element The element that the tooltip should target.
420
+ * @param {string} placement The placement for the tooltip.
421
+ * @param {number} tipWidth Width of the tooltip element in pixels.
422
+ * @param {number} tipHeight Height of the tooltip element in pixels.
423
+ * @param {number} offset Distance to offset tooltips in pixels.
424
+ * @return {CSSCoordinates} A CSSCoordinates object with the position.
425
+ */
426
+ function computePlacementCoords(element, placement, tipWidth, tipHeight, offset) {
427
+ var placementBase = placement.split('-')[0], // ignore 'alt' for corners
428
+ coords = new CSSCoordinates(),
429
+ position;
430
+
431
+ if (isSvgElement(element)) {
432
+ position = getSvgPlacement(element, placementBase);
433
+ } else {
434
+ position = getHtmlPlacement(element, placementBase);
435
+ }
436
+
437
+ // calculate the appropriate x and y position in the document
438
+ switch (placement) {
439
+ case 'n':
440
+ coords.set('left', position.left - (tipWidth / 2));
441
+ coords.set('bottom', session.windowHeight - position.top + offset);
442
+ break;
443
+ case 'e':
444
+ coords.set('left', position.left + offset);
445
+ coords.set('top', position.top - (tipHeight / 2));
446
+ break;
447
+ case 's':
448
+ coords.set('left', position.left - (tipWidth / 2));
449
+ coords.set('top', position.top + offset);
450
+ break;
451
+ case 'w':
452
+ coords.set('top', position.top - (tipHeight / 2));
453
+ coords.set('right', session.windowWidth - position.left + offset);
454
+ break;
455
+ case 'nw':
456
+ coords.set('bottom', session.windowHeight - position.top + offset);
457
+ coords.set('right', session.windowWidth - position.left - 20);
458
+ break;
459
+ case 'nw-alt':
460
+ coords.set('left', position.left);
461
+ coords.set('bottom', session.windowHeight - position.top + offset);
462
+ break;
463
+ case 'ne':
464
+ coords.set('left', position.left - 20);
465
+ coords.set('bottom', session.windowHeight - position.top + offset);
466
+ break;
467
+ case 'ne-alt':
468
+ coords.set('bottom', session.windowHeight - position.top + offset);
469
+ coords.set('right', session.windowWidth - position.left);
470
+ break;
471
+ case 'sw':
472
+ coords.set('top', position.top + offset);
473
+ coords.set('right', session.windowWidth - position.left - 20);
474
+ break;
475
+ case 'sw-alt':
476
+ coords.set('left', position.left);
477
+ coords.set('top', position.top + offset);
478
+ break;
479
+ case 'se':
480
+ coords.set('left', position.left - 20);
481
+ coords.set('top', position.top + offset);
482
+ break;
483
+ case 'se-alt':
484
+ coords.set('top', position.top + offset);
485
+ coords.set('right', session.windowWidth - position.left);
486
+ break;
487
+ }
488
+
489
+ return coords;
490
+ }
491
+
492
+ /**
493
+ * Finds the tooltip attachment point in the document for a HTML DOM element
494
+ * for the specified placement.
495
+ * @private
496
+ * @param {jQuery} element The element that the tooltip should target.
497
+ * @param {string} placement The placement for the tooltip.
498
+ * @return {Object} An object with the top,left position values.
499
+ */
500
+ function getHtmlPlacement(element, placement) {
501
+ var objectOffset = element.offset(),
502
+ objectWidth = element.outerWidth(),
503
+ objectHeight = element.outerHeight(),
504
+ left,
505
+ top;
506
+
507
+ // calculate the appropriate x and y position in the document
508
+ switch (placement) {
509
+ case 'n':
510
+ left = objectOffset.left + objectWidth / 2;
511
+ top = objectOffset.top;
512
+ break;
513
+ case 'e':
514
+ left = objectOffset.left + objectWidth;
515
+ top = objectOffset.top + objectHeight / 2;
516
+ break;
517
+ case 's':
518
+ left = objectOffset.left + objectWidth / 2;
519
+ top = objectOffset.top + objectHeight;
520
+ break;
521
+ case 'w':
522
+ left = objectOffset.left;
523
+ top = objectOffset.top + objectHeight / 2;
524
+ break;
525
+ case 'nw':
526
+ left = objectOffset.left;
527
+ top = objectOffset.top;
528
+ break;
529
+ case 'ne':
530
+ left = objectOffset.left + objectWidth;
531
+ top = objectOffset.top;
532
+ break;
533
+ case 'sw':
534
+ left = objectOffset.left;
535
+ top = objectOffset.top + objectHeight;
536
+ break;
537
+ case 'se':
538
+ left = objectOffset.left + objectWidth;
539
+ top = objectOffset.top + objectHeight;
540
+ break;
541
+ }
542
+
543
+ return {
544
+ top: top,
545
+ left: left
546
+ };
547
+ }
548
+
549
+ /**
550
+ * Finds the tooltip attachment point in the document for a SVG element for
551
+ * the specified placement.
552
+ * @private
553
+ * @param {jQuery} element The element that the tooltip should target.
554
+ * @param {string} placement The placement for the tooltip.
555
+ * @return {Object} An object with the top,left position values.
556
+ */
557
+ function getSvgPlacement(element, placement) {
558
+ var svgElement = element.closest('svg')[0],
559
+ domElement = element[0],
560
+ point = svgElement.createSVGPoint(),
561
+ boundingBox = domElement.getBBox(),
562
+ matrix = domElement.getScreenCTM(),
563
+ halfWidth = boundingBox.width / 2,
564
+ halfHeight = boundingBox.height / 2,
565
+ placements = [],
566
+ placementKeys = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'],
567
+ coords,
568
+ rotation,
569
+ steps,
570
+ x;
571
+
572
+ function pushPlacement() {
573
+ placements.push(point.matrixTransform(matrix));
574
+ }
575
+
576
+ // get bounding box corners and midpoints
577
+ point.x = boundingBox.x;
578
+ point.y = boundingBox.y;
579
+ pushPlacement();
580
+ point.x += halfWidth;
581
+ pushPlacement();
582
+ point.x += halfWidth;
583
+ pushPlacement();
584
+ point.y += halfHeight;
585
+ pushPlacement();
586
+ point.y += halfHeight;
587
+ pushPlacement();
588
+ point.x -= halfWidth;
589
+ pushPlacement();
590
+ point.x -= halfWidth;
591
+ pushPlacement();
592
+ point.y -= halfHeight;
593
+ pushPlacement();
594
+
595
+ // determine rotation
596
+ if (placements[0].y !== placements[1].y || placements[0].x !== placements[7].x) {
597
+ rotation = Math.atan2(matrix.b, matrix.a) * RAD2DEG;
598
+ steps = Math.ceil(((rotation % 360) - 22.5) / 45);
599
+ if (steps < 1) {
600
+ steps += 8;
601
+ }
602
+ while (steps--) {
603
+ placementKeys.push(placementKeys.shift());
604
+ }
605
+ }
606
+
607
+ // find placement
608
+ for (x = 0; x < placements.length; x++) {
609
+ if (placementKeys[x] === placement) {
610
+ coords = placements[x];
611
+ break;
612
+ }
613
+ }
614
+
615
+ return {
616
+ top: coords.y + session.scrollTop,
617
+ left: coords.x + session.scrollLeft
618
+ };
619
+ }
620
+
621
+ // expose methods
622
+ this.compute = computePlacementCoords;
279
623
  }
280
624
 
281
625
  /**
@@ -285,13 +629,14 @@
285
629
  * @param {Object} options Options object containing settings.
286
630
  */
287
631
  function TooltipController(options) {
632
+ var placementCalculator = new PlacementCalculator(),
633
+ tipElement = $('#' + options.popupId);
288
634
 
289
- // build and append popup div if it does not already exist
290
- var tipElement = $('#' + options.popupId);
635
+ // build and append tooltip div if it does not already exist
291
636
  if (tipElement.length === 0) {
292
- tipElement = $('<div></div>', { id: options.popupId });
637
+ tipElement = $('<div/>', { id: options.popupId });
293
638
  // grab body element if it was not populated when the script loaded
294
- // this hack exists solely for jsfiddle support
639
+ // note: this hack exists solely for jsfiddle support
295
640
  if ($body.length === 0) {
296
641
  $body = $('body');
297
642
  }
@@ -300,79 +645,79 @@
300
645
 
301
646
  // hook mousemove for cursor follow tooltips
302
647
  if (options.followMouse) {
303
- // only one positionTipOnCursor hook per popup element, please
304
- if (!tipElement.data('hasMouseMove')) {
305
- $document.on({
306
- mousemove: positionTipOnCursor,
307
- scroll: positionTipOnCursor
308
- });
648
+ // only one positionTipOnCursor hook per tooltip element, please
649
+ if (!tipElement.data(DATA_HASMOUSEMOVE)) {
650
+ $document.on('mousemove', positionTipOnCursor);
651
+ $window.on('scroll', positionTipOnCursor);
652
+ tipElement.data(DATA_HASMOUSEMOVE, true);
309
653
  }
310
- tipElement.data('hasMouseMove', true);
311
654
  }
312
655
 
313
- // if we want to be able to mouse onto the popup then we need to attach
314
- // hover events to the popup that will cancel a close request on hover
315
- // and start a new close request on mouseleave
316
- if (options.followMouse || options.mouseOnToPopup) {
656
+ // if we want to be able to mouse onto the tooltip then we need to attach
657
+ // hover events to the tooltip that will cancel a close request on hover and
658
+ // start a new close request on mouseleave
659
+ if (options.mouseOnToPopup) {
317
660
  tipElement.on({
318
- mouseenter: function() {
319
- if (tipElement.data('followMouse') || tipElement.data('mouseOnToPopup')) {
320
- // check activeHover in case the mouse cursor entered
321
- // the tooltip during the fadeOut and close cycle
661
+ mouseenter: function tipMouseEnter() {
662
+ // we only let the mouse stay on the tooltip if it is set to let
663
+ // users interact with it
664
+ if (tipElement.data(DATA_MOUSEONTOTIP)) {
665
+ // check activeHover in case the mouse cursor entered the
666
+ // tooltip during the fadeOut and close cycle
322
667
  if (session.activeHover) {
323
- session.activeHover.data('displayController').cancel();
668
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
324
669
  }
325
670
  }
326
671
  },
327
- mouseleave: function() {
328
- if (tipElement.data('mouseOnToPopup')) {
329
- // check activeHover in case the mouse cursor entered
330
- // the tooltip during the fadeOut and close cycle
331
- if (session.activeHover) {
332
- session.activeHover.data('displayController').hide();
333
- }
672
+ mouseleave: function tipMouseLeave() {
673
+ // check activeHover in case the mouse cursor entered the
674
+ // tooltip during the fadeOut and close cycle
675
+ if (session.activeHover) {
676
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
334
677
  }
335
678
  }
336
679
  });
337
680
  }
338
681
 
339
682
  /**
340
- * Gives the specified element the active-hover state and queues up
341
- * the showTip function.
683
+ * Gives the specified element the active-hover state and queues up the
684
+ * showTip function.
342
685
  * @private
343
- * @param {Object} element The element that the tooltip should target.
686
+ * @param {jQuery} element The element that the tooltip should target.
344
687
  */
345
688
  function beginShowTip(element) {
346
- element.data('hasActiveHover', true);
347
- // show popup, asap
348
- tipElement.queue(function(next) {
689
+ element.data(DATA_HASACTIVEHOVER, true);
690
+ // show tooltip, asap
691
+ tipElement.queue(function queueTipInit(next) {
349
692
  showTip(element);
350
693
  next();
351
694
  });
352
695
  }
353
696
 
354
697
  /**
355
- * Shows the tooltip popup, as soon as possible.
698
+ * Shows the tooltip, as soon as possible.
356
699
  * @private
357
- * @param {Object} element The element that the popup should target.
700
+ * @param {jQuery} element The element that the tooltip should target.
358
701
  */
359
702
  function showTip(element) {
360
- // it is possible, especially with keyboard navigation, to move on
361
- // to another element with a tooltip during the queue to get to
362
- // this point in the code. if that happens then we need to not
363
- // proceed or we may have the fadeout callback for the last tooltip
364
- // execute immediately after this code runs, causing bugs.
365
- if (!element.data('hasActiveHover')) {
703
+ var tipContent;
704
+
705
+ // it is possible, especially with keyboard navigation, to move on to
706
+ // another element with a tooltip during the queue to get to this point
707
+ // in the code. if that happens then we need to not proceed or we may
708
+ // have the fadeout callback for the last tooltip execute immediately
709
+ // after this code runs, causing bugs.
710
+ if (!element.data(DATA_HASACTIVEHOVER)) {
366
711
  return;
367
712
  }
368
713
 
369
- // if the popup is open and we got asked to open another one then
370
- // the old one is still in its fadeOut cycle, so wait and try again
371
- if (session.isPopOpen) {
714
+ // if the tooltip is open and we got asked to open another one then the
715
+ // old one is still in its fadeOut cycle, so wait and try again
716
+ if (session.isTipOpen) {
372
717
  if (!session.isClosing) {
373
718
  hideTip(session.activeHover);
374
719
  }
375
- tipElement.delay(100).queue(function(next) {
720
+ tipElement.delay(100).queue(function queueTipAgain(next) {
376
721
  showTip(element);
377
722
  next();
378
723
  });
@@ -382,19 +727,10 @@
382
727
  // trigger powerTipPreRender event
383
728
  element.trigger('powerTipPreRender');
384
729
 
385
- var tipText = element.data('powertip'),
386
- tipTarget = element.data('powertiptarget'),
387
- tipElem = element.data('powertipjq'),
388
- tipContent = tipTarget ? $('#' + tipTarget) : [];
389
-
390
- // set popup content
391
- if (tipText) {
392
- tipElement.html(tipText);
393
- } else if (tipElem && tipElem.length > 0) {
394
- tipElement.empty();
395
- tipElem.clone(true, true).appendTo(tipElement);
396
- } else if (tipContent && tipContent.length > 0) {
397
- tipElement.html($('#' + tipTarget).html());
730
+ // set tooltip content
731
+ tipContent = getTooltipContent(element);
732
+ if (tipContent) {
733
+ tipElement.empty().append(tipContent);
398
734
  } else {
399
735
  // we have no content to display, give up
400
736
  return;
@@ -403,27 +739,21 @@
403
739
  // trigger powerTipRender event
404
740
  element.trigger('powerTipRender');
405
741
 
406
- // hook close event for triggering from the api
407
- $document.on('closePowerTip', function() {
408
- element.data('displayController').hide(true);
409
- });
410
-
411
742
  session.activeHover = element;
412
- session.isPopOpen = true;
743
+ session.isTipOpen = true;
413
744
 
414
- tipElement.data('followMouse', options.followMouse);
415
- tipElement.data('mouseOnToPopup', options.mouseOnToPopup);
745
+ tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);
416
746
 
417
- // set popup position
747
+ // set tooltip position
418
748
  if (!options.followMouse) {
419
749
  positionTipOnElement(element);
420
- session.isFixedPopOpen = true;
750
+ session.isFixedTipOpen = true;
421
751
  } else {
422
752
  positionTipOnCursor();
423
753
  }
424
754
 
425
755
  // fadein
426
- tipElement.fadeIn(options.fadeInTime, function() {
756
+ tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
427
757
  // start desync polling
428
758
  if (!session.desyncTimeout) {
429
759
  session.desyncTimeout = setInterval(closeDesyncedTip, 500);
@@ -435,33 +765,37 @@
435
765
  }
436
766
 
437
767
  /**
438
- * Hides the tooltip popup, immediately.
768
+ * Hides the tooltip.
439
769
  * @private
440
- * @param {Object} element The element that the popup should target.
770
+ * @param {jQuery} element The element that the tooltip should target.
441
771
  */
442
772
  function hideTip(element) {
443
- session.isClosing = true;
444
- element.data('hasActiveHover', false);
445
- element.data('forcedOpen', false);
446
773
  // reset session
774
+ session.isClosing = true;
447
775
  session.activeHover = null;
448
- session.isPopOpen = false;
776
+ session.isTipOpen = false;
777
+
449
778
  // stop desync polling
450
779
  session.desyncTimeout = clearInterval(session.desyncTimeout);
451
- // unhook close event api listener
452
- $document.off('closePowerTip');
780
+
781
+ // reset element state
782
+ element.data(DATA_HASACTIVEHOVER, false);
783
+ element.data(DATA_FORCEDOPEN, false);
784
+
453
785
  // fade out
454
- tipElement.fadeOut(options.fadeOutTime, function() {
786
+ tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
787
+ var coords = new CSSCoordinates();
788
+
789
+ // reset session and tooltip element
455
790
  session.isClosing = false;
456
- session.isFixedPopOpen = false;
791
+ session.isFixedTipOpen = false;
457
792
  tipElement.removeClass();
458
- // support mouse-follow and fixed position pops at the same
459
- // time by moving the popup to the last known cursor location
460
- // after it is hidden
461
- setTipPosition(
462
- session.currentX + options.offset,
463
- session.currentY + options.offset
464
- );
793
+
794
+ // support mouse-follow and fixed position tips at the same time by
795
+ // moving the tooltip to the last cursor location after it is hidden
796
+ coords.set('top', session.currentY + options.offset);
797
+ coords.set('left', session.currentX + options.offset);
798
+ tipElement.css(coords);
465
799
 
466
800
  // trigger powerTipClose event
467
801
  element.trigger('powerTipClose');
@@ -469,268 +803,245 @@
469
803
  }
470
804
 
471
805
  /**
472
- * Checks for a tooltip desync and closes the tooltip if one occurs.
473
- * @private
474
- */
475
- function closeDesyncedTip() {
476
- // It is possible for the mouse cursor to leave an element without
477
- // firing the mouseleave event. This seems to happen (in FF) if the
478
- // element is disabled under mouse cursor, the element is moved out
479
- // from under the mouse cursor (such as a slideDown() occurring
480
- // above it), or if the browser is resized by code moving the
481
- // element from under the mouse cursor. If this happens it will
482
- // result in a desynced tooltip because we wait for any exiting
483
- // open tooltips to close before opening a new one. So we should
484
- // periodically check for a desync situation and close the tip if
485
- // such a situation arises.
486
- if (session.isPopOpen && !session.isClosing) {
487
- var isDesynced = false;
488
-
489
- // case 1: user already moused onto another tip - easy test
490
- if (session.activeHover.data('hasActiveHover') === false) {
491
- isDesynced = true;
492
- } else {
493
- // case 2: hanging tip - have to test if mouse position is
494
- // not over the active hover and not over a tooltip set to
495
- // let the user interact with it.
496
- // for keyboard navigation, this only counts if the element
497
- // does not have focus.
498
- // for tooltips opened via the api we need to check if it
499
- // has the forcedOpen flag.
500
- if (!isMouseOver(session.activeHover) && !session.activeHover.is(":focus") && !session.activeHover.data('forcedOpen')) {
501
- if (tipElement.data('mouseOnToPopup')) {
502
- if (!isMouseOver(tipElement)) {
503
- isDesynced = true;
504
- }
505
- } else {
506
- isDesynced = true;
507
- }
508
- }
509
- }
510
-
511
- if (isDesynced) {
512
- // close the desynced tip
513
- hideTip(session.activeHover);
514
- }
515
- }
516
- }
517
-
518
- /**
519
- * Moves the tooltip popup to the users mouse cursor.
806
+ * Moves the tooltip to the users mouse cursor.
520
807
  * @private
521
808
  */
522
809
  function positionTipOnCursor() {
523
- // to support having fixed powertips on the same page as cursor
524
- // powertips, where both instances are referencing the same popup
525
- // element, we need to keep track of the mouse position constantly,
526
- // but we should only set the pop location if a fixed pop is not
527
- // currently open, a pop open is imminent or active, and the popup
528
- // element in question does have a mouse-follow using it.
529
- if ((session.isPopOpen && !session.isFixedPopOpen) || (session.popOpenImminent && !session.isFixedPopOpen && tipElement.data('hasMouseMove'))) {
810
+ // to support having fixed tooltips on the same page as cursor tooltips,
811
+ // where both instances are referencing the same tooltip element, we
812
+ // need to keep track of the mouse position constantly, but we should
813
+ // only set the tip location if a fixed tip is not currently open, a tip
814
+ // open is imminent or active, and the tooltip element in question does
815
+ // have a mouse-follow using it.
816
+ if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
530
817
  // grab measurements
531
- var scrollTop = $window.scrollTop(),
532
- windowWidth = $window.width(),
533
- windowHeight = $window.height(),
534
- popWidth = tipElement.outerWidth(),
535
- popHeight = tipElement.outerHeight(),
536
- x = 0,
537
- y = 0;
538
-
539
- // constrain pop to browser viewport
540
- if ((popWidth + session.currentX + options.offset) < windowWidth) {
541
- x = session.currentX + options.offset;
542
- } else {
543
- x = windowWidth - popWidth;
544
- }
545
- if ((popHeight + session.currentY + options.offset) < (scrollTop + windowHeight)) {
546
- y = session.currentY + options.offset;
547
- } else {
548
- y = scrollTop + windowHeight - popHeight;
818
+ var tipWidth = tipElement.outerWidth(),
819
+ tipHeight = tipElement.outerHeight(),
820
+ coords = new CSSCoordinates(),
821
+ collisions,
822
+ collisionCount;
823
+
824
+ // grab collisions
825
+ coords.set('top', session.currentY + options.offset);
826
+ coords.set('left', session.currentX + options.offset);
827
+ collisions = getViewportCollisions(
828
+ coords,
829
+ tipWidth,
830
+ tipHeight
831
+ );
832
+
833
+ // handle tooltip view port collisions
834
+ if (collisions !== Collision.none) {
835
+ collisionCount = countFlags(collisions);
836
+ if (collisionCount === 1) {
837
+ // if there is only one collision (bottom or right) then
838
+ // simply constrain the tooltip to the view port
839
+ if (collisions === Collision.right) {
840
+ coords.set('left', session.windowWidth - tipWidth);
841
+ } else if (collisions === Collision.bottom) {
842
+ coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
843
+ }
844
+ } else {
845
+ // if the tooltip has more than one collision then it is
846
+ // trapped in the corner and should be flipped to get it out
847
+ // of the users way
848
+ coords.set('left', session.currentX - tipWidth - options.offset);
849
+ coords.set('top', session.currentY - tipHeight - options.offset);
850
+ }
549
851
  }
550
852
 
551
853
  // position the tooltip
552
- setTipPosition(x, y);
854
+ tipElement.css(coords);
553
855
  }
554
856
  }
555
857
 
556
858
  /**
557
- * Sets the tooltip popup too the correct position relative to the
558
- * specified target element. Based on options settings.
859
+ * Sets the tooltip to the correct position relative to the specified target
860
+ * element. Based on options settings.
559
861
  * @private
560
- * @param {Object} element The element that the popup should target.
862
+ * @param {jQuery} element The element that the tooltip should target.
561
863
  */
562
864
  function positionTipOnElement(element) {
563
- var tipWidth = tipElement.outerWidth(),
564
- tipHeight = tipElement.outerHeight(),
565
- priorityList,
566
- placementCoords,
567
- finalPlacement,
568
- collisions;
569
-
570
- // with smart placement we will try a series of placement
571
- // options and use the first one that does not collide with the
572
- // browser view port boundaries.
573
- if (options.smartPlacement) {
865
+ var priorityList,
866
+ finalPlacement;
574
867
 
575
- // grab the placement priority list
868
+ if (options.smartPlacement) {
576
869
  priorityList = $.fn.powerTip.smartPlacementLists[options.placement];
577
870
 
578
- // iterate over the priority list and use the first placement
579
- // option that does not collide with the viewport. if they all
580
- // collide then the last placement in the list will be used.
871
+ // iterate over the priority list and use the first placement option
872
+ // that does not collide with the view port. if they all collide
873
+ // then the last placement in the list will be used.
581
874
  $.each(priorityList, function(idx, pos) {
582
- // get placement coordinates
583
- placementCoords = computePlacementCoords(
584
- element,
585
- pos,
586
- tipWidth,
587
- tipHeight
875
+ // place tooltip and find collisions
876
+ var collisions = getViewportCollisions(
877
+ placeTooltip(element, pos),
878
+ tipElement.outerWidth(),
879
+ tipElement.outerHeight()
588
880
  );
589
- finalPlacement = pos;
590
881
 
591
- // find collisions
592
- collisions = getViewportCollisions(
593
- placementCoords,
594
- tipWidth,
595
- tipHeight
596
- );
882
+ // update the final placement variable
883
+ finalPlacement = pos;
597
884
 
598
885
  // break if there were no collisions
599
- if (collisions.length === 0) {
886
+ if (collisions === Collision.none) {
600
887
  return false;
601
888
  }
602
889
  });
603
-
604
890
  } else {
605
-
606
- // if we're not going to use the smart placement feature then
607
- // just compute the coordinates and do it
608
- placementCoords = computePlacementCoords(
609
- element,
610
- options.placement,
611
- tipWidth,
612
- tipHeight
613
- );
891
+ // if we're not going to use the smart placement feature then just
892
+ // compute the coordinates and do it
893
+ placeTooltip(element, options.placement);
614
894
  finalPlacement = options.placement;
615
-
616
895
  }
617
896
 
618
897
  // add placement as class for CSS arrows
619
898
  tipElement.addClass(finalPlacement);
620
-
621
- // position the tooltip
622
- setTipPosition(placementCoords.x, placementCoords.y);
623
899
  }
624
900
 
625
901
  /**
626
- * Compute the top/left coordinates to display the tooltip at the
627
- * specified placement relative to the specified element.
902
+ * Sets the tooltip position to the appropriate values to show the tip at
903
+ * the specified placement. This function will iterate and test the tooltip
904
+ * to support elastic tooltips.
628
905
  * @private
629
- * @param {Object} element The element that the tooltip should target.
630
- * @param {String} placement The placement for the tooltip.
631
- * @param {Number} popWidth Width of the tooltip element in pixels.
632
- * @param {Number} popHeight Height of the tooltip element in pixels.
633
- * @retun {Object} An object with the x and y coordinates.
906
+ * @param {jQuery} element The element that the tooltip should target.
907
+ * @param {string} placement The placement for the tooltip.
908
+ * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
909
+ * right position values.
634
910
  */
635
- function computePlacementCoords(element, placement, popWidth, popHeight) {
636
- // grab measurements
637
- var objectOffset = element.offset(),
638
- objectWidth = element.outerWidth(),
639
- objectHeight = element.outerHeight(),
640
- x = 0,
641
- y = 0;
911
+ function placeTooltip(element, placement) {
912
+ var iterationCount = 0,
913
+ tipWidth,
914
+ tipHeight,
915
+ coords = new CSSCoordinates();
916
+
917
+ // set the tip to 0,0 to get the full expanded width
918
+ coords.set('top', 0);
919
+ coords.set('left', 0);
920
+ tipElement.css(coords);
921
+
922
+ // to support elastic tooltips we need to check for a change in the
923
+ // rendered dimensions after the tooltip has been positioned
924
+ do {
925
+ // grab the current tip dimensions
926
+ tipWidth = tipElement.outerWidth();
927
+ tipHeight = tipElement.outerHeight();
928
+
929
+ // get placement coordinates
930
+ coords = placementCalculator.compute(
931
+ element,
932
+ placement,
933
+ tipWidth,
934
+ tipHeight,
935
+ options.offset
936
+ );
642
937
 
643
- // calculate the appropriate x and y position in the document
644
- switch (placement) {
645
- case 'n':
646
- x = (objectOffset.left + (objectWidth / 2)) - (popWidth / 2);
647
- y = objectOffset.top - popHeight - options.offset;
648
- break;
649
- case 'e':
650
- x = objectOffset.left + objectWidth + options.offset;
651
- y = (objectOffset.top + (objectHeight / 2)) - (popHeight / 2);
652
- break;
653
- case 's':
654
- x = (objectOffset.left + (objectWidth / 2)) - (popWidth / 2);
655
- y = objectOffset.top + objectHeight + options.offset;
656
- break;
657
- case 'w':
658
- x = objectOffset.left - popWidth - options.offset;
659
- y = (objectOffset.top + (objectHeight / 2)) - (popHeight / 2);
660
- break;
661
- case 'nw':
662
- x = (objectOffset.left - popWidth) + 20;
663
- y = objectOffset.top - popHeight - options.offset;
664
- break;
665
- case 'ne':
666
- x = (objectOffset.left + objectWidth) - 20;
667
- y = objectOffset.top - popHeight - options.offset;
668
- break;
669
- case 'sw':
670
- x = (objectOffset.left - popWidth) + 20;
671
- y = objectOffset.top + objectHeight + options.offset;
672
- break;
673
- case 'se':
674
- x = (objectOffset.left + objectWidth) - 20;
675
- y = objectOffset.top + objectHeight + options.offset;
676
- break;
677
- }
938
+ // place the tooltip
939
+ tipElement.css(coords);
940
+ } while (
941
+ // sanity check: limit to 5 iterations, and...
942
+ ++iterationCount <= 5 &&
943
+ // try again if the dimensions changed after placement
944
+ (tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
945
+ );
678
946
 
679
- return {
680
- x: Math.round(x),
681
- y: Math.round(y)
682
- };
947
+ return coords;
683
948
  }
684
949
 
685
950
  /**
686
- * Sets the tooltip CSS position on the document.
951
+ * Checks for a tooltip desync and closes the tooltip if one occurs.
687
952
  * @private
688
- * @param {Number} x Left position in pixels.
689
- * @param {Number} y Top position in pixels.
690
953
  */
691
- function setTipPosition(x, y) {
692
- tipElement.css('left', x + 'px');
693
- tipElement.css('top', y + 'px');
954
+ function closeDesyncedTip() {
955
+ var isDesynced = false;
956
+ // It is possible for the mouse cursor to leave an element without
957
+ // firing the mouseleave or blur event. This most commonly happens when
958
+ // the element is disabled under mouse cursor. If this happens it will
959
+ // result in a desynced tooltip because the tooltip was never asked to
960
+ // close. So we should periodically check for a desync situation and
961
+ // close the tip if such a situation arises.
962
+ if (session.isTipOpen && !session.isClosing && !session.delayInProgress) {
963
+ // user moused onto another tip or active hover is disabled
964
+ if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
965
+ isDesynced = true;
966
+ } else {
967
+ // hanging tip - have to test if mouse position is not over the
968
+ // active hover and not over a tooltip set to let the user
969
+ // interact with it.
970
+ // for keyboard navigation: this only counts if the element does
971
+ // not have focus.
972
+ // for tooltips opened via the api: we need to check if it has
973
+ // the forcedOpen flag.
974
+ if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
975
+ if (tipElement.data(DATA_MOUSEONTOTIP)) {
976
+ if (!isMouseOver(tipElement)) {
977
+ isDesynced = true;
978
+ }
979
+ } else {
980
+ isDesynced = true;
981
+ }
982
+ }
983
+ }
984
+
985
+ if (isDesynced) {
986
+ // close the desynced tip
987
+ hideTip(session.activeHover);
988
+ }
989
+ }
694
990
  }
695
991
 
696
992
  // expose methods
697
- return {
698
- showTip: beginShowTip,
699
- hideTip: hideTip
700
- };
993
+ this.showTip = beginShowTip;
994
+ this.hideTip = hideTip;
995
+ this.resetPosition = positionTipOnElement;
701
996
  }
702
997
 
703
998
  /**
704
- * Hooks mouse position tracking to mousemove and scroll events.
705
- * Prevents attaching the events more than once.
999
+ * Determine whether a jQuery object is an SVG element
706
1000
  * @private
1001
+ * @param {jQuery} element The element to check
1002
+ * @return {boolean} Whether this is an SVG element
707
1003
  */
708
- function initMouseTracking() {
709
- var lastScrollX = 0,
710
- lastScrollY = 0;
1004
+ function isSvgElement(element) {
1005
+ return window.SVGElement && element[0] instanceof SVGElement;
1006
+ }
711
1007
 
1008
+ /**
1009
+ * Initializes the viewport dimension cache and hooks up the mouse position
1010
+ * tracking and viewport dimension tracking events.
1011
+ * Prevents attaching the events more than once.
1012
+ * @private
1013
+ */
1014
+ function initTracking() {
712
1015
  if (!session.mouseTrackingActive) {
713
1016
  session.mouseTrackingActive = true;
714
1017
 
715
- // grab the current scroll position on load
716
- $(function() {
717
- lastScrollX = $document.scrollLeft();
718
- lastScrollY = $document.scrollTop();
1018
+ // grab the current viewport dimensions on load
1019
+ $(function getViewportDimensions() {
1020
+ session.scrollLeft = $window.scrollLeft();
1021
+ session.scrollTop = $window.scrollTop();
1022
+ session.windowWidth = $window.width();
1023
+ session.windowHeight = $window.height();
719
1024
  });
720
1025
 
721
- // hook mouse position tracking
722
- $document.on({
723
- mousemove: trackMouse,
724
- scroll: function() {
725
- var x = $document.scrollLeft(),
726
- y = $document.scrollTop();
727
- if (x !== lastScrollX) {
728
- session.currentX += x - lastScrollX;
729
- lastScrollX = x;
1026
+ // hook mouse move tracking
1027
+ $document.on('mousemove', trackMouse);
1028
+
1029
+ // hook viewport dimensions tracking
1030
+ $window.on({
1031
+ resize: function trackResize() {
1032
+ session.windowWidth = $window.width();
1033
+ session.windowHeight = $window.height();
1034
+ },
1035
+ scroll: function trackScroll() {
1036
+ var x = $window.scrollLeft(),
1037
+ y = $window.scrollTop();
1038
+ if (x !== session.scrollLeft) {
1039
+ session.currentX += x - session.scrollLeft;
1040
+ session.scrollLeft = x;
730
1041
  }
731
- if (y !== lastScrollY) {
732
- session.currentY += y - lastScrollY;
733
- lastScrollY = y;
1042
+ if (y !== session.scrollTop) {
1043
+ session.currentY += y - session.scrollTop;
1044
+ session.scrollTop = y;
734
1045
  }
735
1046
  }
736
1047
  });
@@ -738,9 +1049,9 @@
738
1049
  }
739
1050
 
740
1051
  /**
741
- * Saves the current mouse coordinates to the powerTip session object.
1052
+ * Saves the current mouse coordinates to the session object.
742
1053
  * @private
743
- * @param {Object} event The mousemove event for the document.
1054
+ * @param {jQuery.Event} event The mousemove event for the document.
744
1055
  */
745
1056
  function trackMouse(event) {
746
1057
  session.currentX = event.pageX;
@@ -750,47 +1061,105 @@
750
1061
  /**
751
1062
  * Tests if the mouse is currently over the specified element.
752
1063
  * @private
753
- * @param {Object} element The element to check for hover.
754
- * @return {Boolean}
1064
+ * @param {jQuery} element The element to check for hover.
1065
+ * @return {boolean}
755
1066
  */
756
1067
  function isMouseOver(element) {
757
- var elementPosition = element.offset();
1068
+ // use getBoundingClientRect() because jQuery's width() and height()
1069
+ // methods do not work with SVG elements
1070
+ // compute width/height because those properties do not exist on the object
1071
+ // returned by getBoundingClientRect() in older versions of IE
1072
+ var elementPosition = element.offset(),
1073
+ elementBox = element[0].getBoundingClientRect(),
1074
+ elementWidth = elementBox.right - elementBox.left,
1075
+ elementHeight = elementBox.bottom - elementBox.top;
1076
+
758
1077
  return session.currentX >= elementPosition.left &&
759
- session.currentX <= elementPosition.left + element.outerWidth() &&
1078
+ session.currentX <= elementPosition.left + elementWidth &&
760
1079
  session.currentY >= elementPosition.top &&
761
- session.currentY <= elementPosition.top + element.outerHeight();
1080
+ session.currentY <= elementPosition.top + elementHeight;
762
1081
  }
763
1082
 
764
1083
  /**
765
- * Finds any viewport collisions that an element (the tooltip) would have
766
- * if it were absolutely positioned at the specified coordinates.
1084
+ * Fetches the tooltip content from the specified element's data attributes.
767
1085
  * @private
768
- * @param {Object} coords Coordinates for the element. (e.g. {x: 123, y: 123})
769
- * @param {Number} elementWidth Width of the element in pixels.
770
- * @param {Number} elementHeight Height of the element in pixels.
771
- * @return {Array} Array of words representing directional collisions.
1086
+ * @param {jQuery} element The element to get the tooltip content for.
1087
+ * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or
1088
+ * undefined if there was no tooltip content for the element.
1089
+ */
1090
+ function getTooltipContent(element) {
1091
+ var tipText = element.data(DATA_POWERTIP),
1092
+ tipObject = element.data(DATA_POWERTIPJQ),
1093
+ tipTarget = element.data(DATA_POWERTIPTARGET),
1094
+ targetElement,
1095
+ content;
1096
+
1097
+ if (tipText) {
1098
+ if ($.isFunction(tipText)) {
1099
+ tipText = tipText.call(element[0]);
1100
+ }
1101
+ content = tipText;
1102
+ } else if (tipObject) {
1103
+ if ($.isFunction(tipObject)) {
1104
+ tipObject = tipObject.call(element[0]);
1105
+ }
1106
+ if (tipObject.length > 0) {
1107
+ content = tipObject.clone(true, true);
1108
+ }
1109
+ } else if (tipTarget) {
1110
+ targetElement = $('#' + tipTarget);
1111
+ if (targetElement.length > 0) {
1112
+ content = targetElement.html();
1113
+ }
1114
+ }
1115
+
1116
+ return content;
1117
+ }
1118
+
1119
+ /**
1120
+ * Finds any viewport collisions that an element (the tooltip) would have if it
1121
+ * were absolutely positioned at the specified coordinates.
1122
+ * @private
1123
+ * @param {CSSCoordinates} coords Coordinates for the element.
1124
+ * @param {number} elementWidth Width of the element in pixels.
1125
+ * @param {number} elementHeight Height of the element in pixels.
1126
+ * @return {number} Value with the collision flags.
772
1127
  */
773
1128
  function getViewportCollisions(coords, elementWidth, elementHeight) {
774
- var scrollLeft = $window.scrollLeft(),
775
- scrollTop = $window.scrollTop(),
776
- windowWidth = $window.width(),
777
- windowHeight = $window.height(),
778
- collisions = [];
779
-
780
- if (coords.y < scrollTop) {
781
- collisions.push('top');
1129
+ var viewportTop = session.scrollTop,
1130
+ viewportLeft = session.scrollLeft,
1131
+ viewportBottom = viewportTop + session.windowHeight,
1132
+ viewportRight = viewportLeft + session.windowWidth,
1133
+ collisions = Collision.none;
1134
+
1135
+ if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
1136
+ collisions |= Collision.top;
782
1137
  }
783
- if (coords.y + elementHeight > scrollTop + windowHeight) {
784
- collisions.push('bottom');
1138
+ if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
1139
+ collisions |= Collision.bottom;
785
1140
  }
786
- if (coords.x < scrollLeft) {
787
- collisions.push('left');
1141
+ if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
1142
+ collisions |= Collision.left;
788
1143
  }
789
- if (coords.x + elementWidth > scrollLeft + windowWidth) {
790
- collisions.push('right');
1144
+ if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
1145
+ collisions |= Collision.right;
791
1146
  }
792
1147
 
793
1148
  return collisions;
794
1149
  }
795
1150
 
796
- }(jQuery));
1151
+ /**
1152
+ * Counts the number of bits set on a flags value.
1153
+ * @param {number} value The flags value.
1154
+ * @return {number} The number of bits that have been set.
1155
+ */
1156
+ function countFlags(value) {
1157
+ var count = 0;
1158
+ while (value) {
1159
+ value &= value - 1;
1160
+ count++;
1161
+ }
1162
+ return count;
1163
+ }
1164
+
1165
+ }));