smartkiosk-server 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. data/.gitignore +32 -0
  2. data/.rspec +1 -0
  3. data/Capfile +4 -0
  4. data/Gemfile +71 -0
  5. data/Gemfile.lock +439 -0
  6. data/Guardfile +25 -0
  7. data/LICENSE +287 -0
  8. data/README.md +13 -0
  9. data/Rakefile +5 -0
  10. data/app/acquirers/cards_mkb_acquirer.rb +87 -0
  11. data/app/acquirers/cash_acquirer.rb +22 -0
  12. data/app/admin/agents.rb +108 -0
  13. data/app/admin/collections.rb +62 -0
  14. data/app/admin/commissions.rb +87 -0
  15. data/app/admin/gateways.rb +183 -0
  16. data/app/admin/limits.rb +82 -0
  17. data/app/admin/payments.rb +246 -0
  18. data/app/admin/provider_groups.rb +60 -0
  19. data/app/admin/provider_profiles.rb +11 -0
  20. data/app/admin/provider_receipt_templates.rb +82 -0
  21. data/app/admin/providers.rb +201 -0
  22. data/app/admin/rebates.rb +83 -0
  23. data/app/admin/report_results.rb +66 -0
  24. data/app/admin/report_templates.rb +209 -0
  25. data/app/admin/reports.rb +158 -0
  26. data/app/admin/revisions.rb +120 -0
  27. data/app/admin/system_receipt_templates.rb +76 -0
  28. data/app/admin/terminal_builds.rb +39 -0
  29. data/app/admin/terminal_profiles.rb +123 -0
  30. data/app/admin/terminals.rb +388 -0
  31. data/app/admin/users.rb +117 -0
  32. data/app/admin/versions.rb +131 -0
  33. data/app/admin/welcome.rb +64 -0
  34. data/app/assets/fonts/PT_Sans-Web-Bold.eot +0 -0
  35. data/app/assets/fonts/PT_Sans-Web-Bold.svg +6386 -0
  36. data/app/assets/fonts/PT_Sans-Web-Bold.ttf +0 -0
  37. data/app/assets/fonts/PT_Sans-Web-Bold.woff +0 -0
  38. data/app/assets/fonts/PT_Sans-Web-Regular.eot +0 -0
  39. data/app/assets/fonts/PT_Sans-Web-Regular.svg +5650 -0
  40. data/app/assets/fonts/PT_Sans-Web-Regular.ttf +0 -0
  41. data/app/assets/fonts/PT_Sans-Web-Regular.woff +0 -0
  42. data/app/assets/images/chosen-sprite.png +0 -0
  43. data/app/assets/javascripts/active_admin/chosen.js.erb.coffee +12 -0
  44. data/app/assets/javascripts/active_admin/qtip.js.coffee +22 -0
  45. data/app/assets/javascripts/active_admin/sortable_forms.js.coffee +14 -0
  46. data/app/assets/javascripts/active_admin.js.coffee +9 -0
  47. data/app/assets/javascripts/application.js +15 -0
  48. data/app/assets/javascripts/report_templates.js.coffee +10 -0
  49. data/app/assets/javascripts/terminals.js.coffee +12 -0
  50. data/app/assets/stylesheets/active_admin/PIE.css.scss +18 -0
  51. data/app/assets/stylesheets/active_admin/filter_numeric_range.css.scss +14 -0
  52. data/app/assets/stylesheets/active_admin/selectable_check_boxes.css.scss +12 -0
  53. data/app/assets/stylesheets/active_admin/sortable_forms.css.scss +11 -0
  54. data/app/assets/stylesheets/active_admin.css.scss +705 -0
  55. data/app/assets/stylesheets/fonts.css.scss +14 -0
  56. data/app/assets/stylesheets/provider_groups.css.scss +12 -0
  57. data/app/assets/stylesheets/provider_receipt_templates.css.scss +12 -0
  58. data/app/assets/stylesheets/terminals.css.scss +9 -0
  59. data/app/controllers/application_controller.rb +12 -0
  60. data/app/controllers/collections_controller.rb +10 -0
  61. data/app/controllers/payments_controller.rb +73 -0
  62. data/app/controllers/system_receipt_templates_controller.rb +10 -0
  63. data/app/controllers/terminal_builds_controller.rb +5 -0
  64. data/app/controllers/terminal_orders_controller.rb +25 -0
  65. data/app/controllers/terminal_pings_controller.rb +45 -0
  66. data/app/controllers/welcome_controller.rb +5 -0
  67. data/app/helpers/active_admin/report_templates_helper.rb +56 -0
  68. data/app/helpers/active_admin/views_helper.rb +20 -0
  69. data/app/helpers/application_helper.rb +2 -0
  70. data/app/mailers/.gitkeep +0 -0
  71. data/app/models/.gitkeep +0 -0
  72. data/app/models/ability.rb +30 -0
  73. data/app/models/agent.rb +24 -0
  74. data/app/models/collection.rb +65 -0
  75. data/app/models/commission.rb +77 -0
  76. data/app/models/commission_section.rb +75 -0
  77. data/app/models/gateway.rb +99 -0
  78. data/app/models/gateway_attachment.rb +9 -0
  79. data/app/models/gateway_setting.rb +7 -0
  80. data/app/models/gateway_switch.rb +7 -0
  81. data/app/models/limit.rb +77 -0
  82. data/app/models/limit_section.rb +58 -0
  83. data/app/models/payment.rb +322 -0
  84. data/app/models/provider.rb +59 -0
  85. data/app/models/provider_field.rb +7 -0
  86. data/app/models/provider_gateway.rb +47 -0
  87. data/app/models/provider_group.rb +45 -0
  88. data/app/models/provider_profile.rb +9 -0
  89. data/app/models/provider_rebate.rb +69 -0
  90. data/app/models/provider_receipt_template.rb +85 -0
  91. data/app/models/rebate.rb +84 -0
  92. data/app/models/report.rb +71 -0
  93. data/app/models/report_result.rb +11 -0
  94. data/app/models/report_template.rb +56 -0
  95. data/app/models/revision.rb +104 -0
  96. data/app/models/role.rb +48 -0
  97. data/app/models/system_receipt_template.rb +13 -0
  98. data/app/models/terminal.rb +126 -0
  99. data/app/models/terminal_build.rb +62 -0
  100. data/app/models/terminal_order.rb +42 -0
  101. data/app/models/terminal_ping.rb +120 -0
  102. data/app/models/terminal_profile.rb +51 -0
  103. data/app/models/terminal_profile_promotion.rb +18 -0
  104. data/app/models/terminal_profile_provider.rb +11 -0
  105. data/app/models/terminal_profile_provider_group.rb +19 -0
  106. data/app/models/user.rb +38 -0
  107. data/app/models/user_role.rb +45 -0
  108. data/app/reports/agents_report.rb +81 -0
  109. data/app/reports/collections_report.rb +151 -0
  110. data/app/reports/payments_report.rb +189 -0
  111. data/app/reports/terminals_report.rb +123 -0
  112. data/app/uploaders/file_uploader.rb +53 -0
  113. data/app/uploaders/icon_uploader.rb +15 -0
  114. data/app/uploaders/zip_uploader.rb +53 -0
  115. data/app/views/admin/gateways/providers.html.arb +10 -0
  116. data/app/views/admin/revisions/payments.html.arb +28 -0
  117. data/app/views/admin/terminal_profiles/_tree.html.haml +35 -0
  118. data/app/views/admin/terminal_profiles/sort.html.haml +21 -0
  119. data/app/views/admin/terminals/pings.html.arb +55 -0
  120. data/app/views/admin/terminals/upgrade_build.html.arb +30 -0
  121. data/app/views/layouts/application.html.erb +1 -0
  122. data/app/views/welcome/index.html.erb +25 -0
  123. data/app/workers/pay_worker.rb +11 -0
  124. data/app/workers/report_worker.rb +7 -0
  125. data/app/workers/revise_worker.rb +7 -0
  126. data/config/acquiring.yml +13 -0
  127. data/config/application.rb +75 -0
  128. data/config/boot.rb +6 -0
  129. data/config/database.yml +17 -0
  130. data/config/deploy/roundlake-passenger.rb +3 -0
  131. data/config/deploy/roundlake-trinidad.rb +3 -0
  132. data/config/deploy.rb +37 -0
  133. data/config/environment.rb +5 -0
  134. data/config/environments/development.rb +39 -0
  135. data/config/environments/production.rb +67 -0
  136. data/config/environments/test.rb +37 -0
  137. data/config/initializers/active_admin.rb +31 -0
  138. data/config/initializers/backtrace_silencers.rb +7 -0
  139. data/config/initializers/devise.rb +232 -0
  140. data/config/initializers/inflections.rb +15 -0
  141. data/config/initializers/mime_types.rb +5 -0
  142. data/config/initializers/money.rb +6 -0
  143. data/config/initializers/redis.rb +4 -0
  144. data/config/initializers/secret_token.rb +7 -0
  145. data/config/initializers/session_store.rb +8 -0
  146. data/config/initializers/setup_rack.rb +1 -0
  147. data/config/initializers/wrap_parameters.rb +14 -0
  148. data/config/locales/active_admin.ru.yml +71 -0
  149. data/config/locales/activerecord.ru.yml +645 -0
  150. data/config/locales/devise.ru.yml +57 -0
  151. data/config/locales/ru.yml +33 -0
  152. data/config/locales/smartkiosk.gateways.ru.yml +241 -0
  153. data/config/locales/smartkiosk.hardware.ru.yml +38 -0
  154. data/config/locales/smartkiosk.reports.ru.yml +56 -0
  155. data/config/locales/smartkiosk.ru.yml +163 -0
  156. data/config/nginx.conf +9 -0
  157. data/config/redis.yml +9 -0
  158. data/config/routes.rb +48 -0
  159. data/config/schedule.rb +13 -0
  160. data/config/sidekiq.yml +5 -0
  161. data/config/trinidad.yml +6 -0
  162. data/config.ru +4 -0
  163. data/db/migrate/20120825083035_create_terminals.rb +47 -0
  164. data/db/migrate/20120825083305_create_payments.rb +51 -0
  165. data/db/migrate/20120827062618_devise_create_users.rb +50 -0
  166. data/db/migrate/20120827132621_create_admin_notes.rb +17 -0
  167. data/db/migrate/20120827132622_move_admin_notes_to_comments.rb +25 -0
  168. data/db/migrate/20120903193346_create_providers.rb +24 -0
  169. data/db/migrate/20120922075345_create_collections.rb +26 -0
  170. data/db/migrate/20120922080440_create_agents.rb +29 -0
  171. data/db/migrate/20120922081104_create_commissions.rb +10 -0
  172. data/db/migrate/20120927035841_create_gateways.rb +13 -0
  173. data/db/migrate/20120927040626_create_provider_gateways.rb +13 -0
  174. data/db/migrate/20120927091849_create_limits.rb +10 -0
  175. data/db/migrate/20120927164647_create_roles.rb +14 -0
  176. data/db/migrate/20121003131522_create_report_templates.rb +18 -0
  177. data/db/migrate/20121007135652_create_reports.rb +13 -0
  178. data/db/migrate/20121008073905_create_report_results.rb +10 -0
  179. data/db/migrate/20121017114538_create_gateway_settings.rb +12 -0
  180. data/db/migrate/20121017114556_create_gateway_attachments.rb +10 -0
  181. data/db/migrate/20121019132606_create_revisions.rb +19 -0
  182. data/db/migrate/20121023073501_create_gateway_switches.rb +10 -0
  183. data/db/migrate/20121112142743_create_terminal_orders.rb +13 -0
  184. data/db/migrate/20121118191553_create_versions.rb +19 -0
  185. data/db/migrate/20121125093414_create_system_receipt_templates.rb +9 -0
  186. data/db/migrate/20121127192257_create_terminal_builds.rb +10 -0
  187. data/db/migrate/20121212053441_create_rebates.rb +15 -0
  188. data/db/migrate/20121216143855_create_provider_receipt_templates.rb +9 -0
  189. data/db/migrate/20130101091100_create_terminal_profiles.rb +13 -0
  190. data/db/migrate/20130101091734_create_provider_rebates.rb +21 -0
  191. data/db/migrate/20130102164447_create_limit_sections.rb +14 -0
  192. data/db/migrate/20130102164503_create_commission_sections.rb +17 -0
  193. data/db/migrate/20130102171743_create_provider_profiles.rb +10 -0
  194. data/db/migrate/20130103152507_create_provider_fields.rb +17 -0
  195. data/db/migrate/20130103154526_create_provider_groups.rb +10 -0
  196. data/db/migrate/20130104063628_create_terminal_profile_providers.rb +11 -0
  197. data/db/migrate/20130104090957_create_terminal_profile_provider_groups.rb +11 -0
  198. data/db/migrate/20130108091644_create_terminal_profile_promotions.rb +10 -0
  199. data/db/schema.rb +489 -0
  200. data/db/seeds/receipt_templates/payment.txt +19 -0
  201. data/db/seeds/receipt_templates/system/balance.txt +13 -0
  202. data/db/seeds/receipt_templates/system/collection.txt +17 -0
  203. data/db/seeds/receipt_templates/system/touchtest.txt +15 -0
  204. data/db/seeds.rb +18 -0
  205. data/doc/README_FOR_APP +2 -0
  206. data/init.rb +1 -0
  207. data/lib/active_admin/cancan_integration.rb +81 -0
  208. data/lib/active_admin/form_builder_fix.rb +76 -0
  209. data/lib/active_admin/inputs/filter_date_range_input_fix.rb +24 -0
  210. data/lib/active_admin/inputs/filter_multiple_select_input.rb +18 -0
  211. data/lib/active_admin/inputs/filter_numeric_range_input.rb +31 -0
  212. data/lib/active_admin/resource_controller_fix.rb +23 -0
  213. data/lib/active_admin/views/pages/base_fix.rb +13 -0
  214. data/lib/assets/.gitkeep +0 -0
  215. data/lib/blueprints.rb +79 -0
  216. data/lib/date_expander.rb +38 -0
  217. data/lib/dav4rack/build_resource.rb +59 -0
  218. data/lib/formtastic/inputs/selectable_check_boxes.rb +39 -0
  219. data/lib/paper_trail/version_fix.rb +3 -0
  220. data/lib/report_builder.rb +88 -0
  221. data/lib/seeder.rb +103 -0
  222. data/lib/smartkiosk/server/version.rb +5 -0
  223. data/lib/smartkiosk/server.rb +35 -0
  224. data/lib/string_file.rb +12 -0
  225. data/lib/tasks/.gitkeep +0 -0
  226. data/lib/tasks/db.rake +27 -0
  227. data/lib/tasks/dump.rake +53 -0
  228. data/lib/tasks/matrioshka.rake +16 -0
  229. data/log/.gitkeep +0 -0
  230. data/log/gateways/.gitkeep +0 -0
  231. data/public/404.html +26 -0
  232. data/public/422.html +26 -0
  233. data/public/500.html +25 -0
  234. data/public/robots.txt +5 -0
  235. data/script/rails +6 -0
  236. data/smartkiosk-server.gemspec +23 -0
  237. data/spec/controllers/collections_controller_spec.rb +32 -0
  238. data/spec/controllers/payments_controller_spec.rb +123 -0
  239. data/spec/controllers/system_receipt_templates_controller_spec.rb +5 -0
  240. data/spec/controllers/terminal_builds_controller_spec.rb +5 -0
  241. data/spec/controllers/terminal_orders_controller_spec.rb +32 -0
  242. data/spec/controllers/terminal_pings_controller_spec.rb +31 -0
  243. data/spec/spec_helper.rb +66 -0
  244. data/vendor/assets/javascripts/.gitkeep +0 -0
  245. data/vendor/assets/javascripts/chosen.jquery.js +1026 -0
  246. data/vendor/assets/javascripts/datepicker-ru.jquery.js +21 -0
  247. data/vendor/assets/javascripts/nestedSortable.jquery.js +429 -0
  248. data/vendor/assets/javascripts/qtip.jquery.js +3403 -0
  249. data/vendor/assets/stylesheets/.gitkeep +0 -0
  250. data/vendor/assets/stylesheets/chosen.jquery.css +397 -0
  251. data/vendor/assets/stylesheets/modules/PIE.htc +96 -0
  252. data/vendor/assets/stylesheets/qtip.jquery.css.scss +604 -0
  253. data/vendor/plugins/.gitkeep +0 -0
  254. metadata +333 -0
@@ -0,0 +1,3403 @@
1
+ /*! qTip2 - Pretty powerful tooltips - v2.0.0 - 2012-12-21
2
+ * http://craigsworks.com/projects/qtip2/
3
+ * Copyright (c) 2012 Craig Michael Thompson; Licensed MIT, GPL */
4
+
5
+ /*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true */
6
+ /*global window: false, jQuery: false, console: false, define: false */
7
+
8
+ /* Cache window, document, undefined */
9
+ (function( window, document, undefined ) {
10
+
11
+ // Uses AMD or browser globals to create a jQuery plugin.
12
+ (function( factory ) {
13
+ "use strict";
14
+ if(typeof define === 'function' && define.amd) {
15
+ define(['jquery'], factory);
16
+ }
17
+ else if(jQuery && !jQuery.fn.qtip) {
18
+ factory(jQuery);
19
+ }
20
+ }
21
+ (function($) {
22
+ /* This currently causes issues with Safari 6, so for it's disabled */
23
+ //"use strict"; // (Dis)able ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
24
+
25
+ // Munge the primitives - Paul Irish tip
26
+ var TRUE = true,
27
+ FALSE = false,
28
+ NULL = null,
29
+
30
+ // Side names and other stuff
31
+ X = 'x', Y = 'y',
32
+ WIDTH = 'width',
33
+ HEIGHT = 'height',
34
+ TOP = 'top',
35
+ LEFT = 'left',
36
+ BOTTOM = 'bottom',
37
+ RIGHT = 'right',
38
+ CENTER = 'center',
39
+ FLIP = 'flip',
40
+ FLIPINVERT = 'flipinvert',
41
+ SHIFT = 'shift',
42
+
43
+ // Shortcut vars
44
+ QTIP, PLUGINS, MOUSE,
45
+ NAMESPACE = 'qtip',
46
+ usedIDs = {},
47
+ widget = ['ui-widget', 'ui-tooltip'],
48
+ selector = 'div.qtip.'+NAMESPACE,
49
+ defaultClass = NAMESPACE + '-default',
50
+ focusClass = NAMESPACE + '-focus',
51
+ hoverClass = NAMESPACE + '-hover',
52
+ replaceSuffix = '_replacedByqTip',
53
+ oldtitle = 'oldtitle',
54
+ trackingBound;
55
+
56
+ // Store mouse coordinates
57
+ function storeMouse(event)
58
+ {
59
+ MOUSE = {
60
+ pageX: event.pageX,
61
+ pageY: event.pageY,
62
+ type: 'mousemove',
63
+ scrollX: window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft,
64
+ scrollY: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop
65
+ };
66
+ }
67
+ // Option object sanitizer
68
+ function sanitizeOptions(opts)
69
+ {
70
+ var invalid = function(a) { return a === NULL || 'object' !== typeof a; },
71
+ invalidContent = function(c) { return !$.isFunction(c) && ((!c && !c.attr) || c.length < 1 || ('object' === typeof c && !c.jquery && !c.then)); };
72
+
73
+ if(!opts || 'object' !== typeof opts) { return FALSE; }
74
+
75
+ if(invalid(opts.metadata)) {
76
+ opts.metadata = { type: opts.metadata };
77
+ }
78
+
79
+ if('content' in opts) {
80
+ if(invalid(opts.content) || opts.content.jquery) {
81
+ opts.content = { text: opts.content };
82
+ }
83
+
84
+ if(invalidContent(opts.content.text || FALSE)) {
85
+ opts.content.text = FALSE;
86
+ }
87
+
88
+ if('title' in opts.content) {
89
+ if(invalid(opts.content.title)) {
90
+ opts.content.title = { text: opts.content.title };
91
+ }
92
+
93
+ if(invalidContent(opts.content.title.text || FALSE)) {
94
+ opts.content.title.text = FALSE;
95
+ }
96
+ }
97
+ }
98
+
99
+ if('position' in opts && invalid(opts.position)) {
100
+ opts.position = { my: opts.position, at: opts.position };
101
+ }
102
+
103
+ if('show' in opts && invalid(opts.show)) {
104
+ opts.show = opts.show.jquery ? { target: opts.show } : { event: opts.show };
105
+ }
106
+
107
+ if('hide' in opts && invalid(opts.hide)) {
108
+ opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
109
+ }
110
+
111
+ if('style' in opts && invalid(opts.style)) {
112
+ opts.style = { classes: opts.style };
113
+ }
114
+
115
+ // Sanitize plugin options
116
+ $.each(PLUGINS, function() {
117
+ if(this.sanitize) { this.sanitize(opts); }
118
+ });
119
+
120
+ return opts;
121
+ }
122
+
123
+ /*
124
+ * Core plugin implementation
125
+ */
126
+ function QTip(target, options, id, attr)
127
+ {
128
+ // Declare this reference
129
+ var self = this,
130
+ docBody = document.body,
131
+ tooltipID = NAMESPACE + '-' + id,
132
+ isPositioning = 0,
133
+ isDrawing = 0,
134
+ tooltip = $(),
135
+ namespace = '.qtip-' + id,
136
+ disabledClass = 'qtip-disabled',
137
+ elements, cache;
138
+
139
+ // Setup class attributes
140
+ self.id = id;
141
+ self.rendered = FALSE;
142
+ self.destroyed = FALSE;
143
+ self.elements = elements = { target: target };
144
+ self.timers = { img: {} };
145
+ self.options = options;
146
+ self.checks = {};
147
+ self.plugins = {};
148
+ self.cache = cache = {
149
+ event: {},
150
+ target: $(),
151
+ disabled: FALSE,
152
+ attr: attr,
153
+ onTarget: FALSE,
154
+ lastClass: ''
155
+ };
156
+
157
+ function convertNotation(notation)
158
+ {
159
+ var i = 0, obj, option = options,
160
+
161
+ // Split notation into array
162
+ levels = notation.split('.');
163
+
164
+ // Loop through
165
+ while( option = option[ levels[i++] ] ) {
166
+ if(i < levels.length) { obj = option; }
167
+ }
168
+
169
+ return [obj || options, levels.pop()];
170
+ }
171
+
172
+ function createWidgetClass(cls)
173
+ {
174
+ return widget.concat('').join(cls ? '-'+cls+' ' : ' ');
175
+ }
176
+
177
+ function setWidget()
178
+ {
179
+ var on = options.style.widget,
180
+ disabled = tooltip.hasClass(disabledClass);
181
+
182
+ tooltip.removeClass(disabledClass);
183
+ disabledClass = on ? 'ui-state-disabled' : 'qtip-disabled';
184
+ tooltip.toggleClass(disabledClass, disabled);
185
+
186
+ tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(defaultClass, options.style.def && !on);
187
+
188
+ if(elements.content) {
189
+ elements.content.toggleClass( createWidgetClass('content'), on);
190
+ }
191
+ if(elements.titlebar) {
192
+ elements.titlebar.toggleClass( createWidgetClass('header'), on);
193
+ }
194
+ if(elements.button) {
195
+ elements.button.toggleClass(NAMESPACE+'-icon', !on);
196
+ }
197
+ }
198
+
199
+ function removeTitle(reposition)
200
+ {
201
+ if(elements.title) {
202
+ elements.titlebar.remove();
203
+ elements.titlebar = elements.title = elements.button = NULL;
204
+
205
+ // Reposition if enabled
206
+ if(reposition !== FALSE) { self.reposition(); }
207
+ }
208
+ }
209
+
210
+ function createButton()
211
+ {
212
+ var button = options.content.title.button,
213
+ isString = typeof button === 'string',
214
+ close = isString ? button : 'Close tooltip';
215
+
216
+ if(elements.button) { elements.button.remove(); }
217
+
218
+ // Use custom button if one was supplied by user, else use default
219
+ if(button.jquery) {
220
+ elements.button = button;
221
+ }
222
+ else {
223
+ elements.button = $('<a />', {
224
+ 'class': 'qtip-close ' + (options.style.widget ? '' : NAMESPACE+'-icon'),
225
+ 'title': close,
226
+ 'aria-label': close
227
+ })
228
+ .prepend(
229
+ $('<span />', {
230
+ 'class': 'ui-icon ui-icon-close',
231
+ 'html': '&times;'
232
+ })
233
+ );
234
+ }
235
+
236
+ // Create button and setup attributes
237
+ elements.button.appendTo(elements.titlebar || tooltip)
238
+ .attr('role', 'button')
239
+ .click(function(event) {
240
+ if(!tooltip.hasClass(disabledClass)) { self.hide(event); }
241
+ return FALSE;
242
+ });
243
+ }
244
+
245
+ function createTitle()
246
+ {
247
+ var id = tooltipID+'-title';
248
+
249
+ // Destroy previous title element, if present
250
+ if(elements.titlebar) { removeTitle(); }
251
+
252
+ // Create title bar and title elements
253
+ elements.titlebar = $('<div />', {
254
+ 'class': NAMESPACE + '-titlebar ' + (options.style.widget ? createWidgetClass('header') : '')
255
+ })
256
+ .append(
257
+ elements.title = $('<div />', {
258
+ 'id': id,
259
+ 'class': NAMESPACE + '-title',
260
+ 'aria-atomic': TRUE
261
+ })
262
+ )
263
+ .insertBefore(elements.content)
264
+
265
+ // Button-specific events
266
+ .delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
267
+ $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
268
+ })
269
+ .delegate('.qtip-close', 'mouseover mouseout', function(event){
270
+ $(this).toggleClass('ui-state-hover', event.type === 'mouseover');
271
+ });
272
+
273
+ // Create button if enabled
274
+ if(options.content.title.button) { createButton(); }
275
+ }
276
+
277
+ function updateButton(button)
278
+ {
279
+ var elem = elements.button;
280
+
281
+ // Make sure tooltip is rendered and if not, return
282
+ if(!self.rendered) { return FALSE; }
283
+
284
+ if(!button) {
285
+ elem.remove();
286
+ }
287
+ else {
288
+ createButton();
289
+ }
290
+ }
291
+
292
+ function updateTitle(content, reposition)
293
+ {
294
+ var elem = elements.title;
295
+
296
+ // Make sure tooltip is rendered and if not, return
297
+ if(!self.rendered || !content) { return FALSE; }
298
+
299
+ // Use function to parse content
300
+ if($.isFunction(content)) {
301
+ content = content.call(target, cache.event, self);
302
+ }
303
+
304
+ // Remove title if callback returns false or null/undefined (but not '')
305
+ if(content === FALSE || (!content && content !== '')) { return removeTitle(FALSE); }
306
+
307
+ // Append new content if its a DOM array and show it if hidden
308
+ else if(content.jquery && content.length > 0) {
309
+ elem.empty().append(content.css({ display: 'block' }));
310
+ }
311
+
312
+ // Content is a regular string, insert the new content
313
+ else { elem.html(content); }
314
+
315
+ // Reposition if rnedered
316
+ if(reposition !== FALSE && self.rendered && tooltip[0].offsetWidth > 0) {
317
+ self.reposition(cache.event);
318
+ }
319
+ }
320
+
321
+ function deferredContent(deferred)
322
+ {
323
+ if(deferred && $.isFunction(deferred.done)) {
324
+ deferred.done(function(c) {
325
+ updateContent(c, null, FALSE);
326
+ });
327
+ }
328
+ }
329
+
330
+ function updateContent(content, reposition, checkDeferred)
331
+ {
332
+ var elem = elements.content;
333
+
334
+ // Make sure tooltip is rendered and content is defined. If not return
335
+ if(!self.rendered || !content) { return FALSE; }
336
+
337
+ // Use function to parse content
338
+ if($.isFunction(content)) {
339
+ content = content.call(target, cache.event, self) || '';
340
+ }
341
+
342
+ // Handle deferred content
343
+ if(checkDeferred !== FALSE) {
344
+ deferredContent(options.content.deferred);
345
+ }
346
+
347
+ // Append new content if its a DOM array and show it if hidden
348
+ if(content.jquery && content.length > 0) {
349
+ elem.empty().append(content.css({ display: 'block' }));
350
+ }
351
+
352
+ // Content is a regular string, insert the new content
353
+ else { elem.html(content); }
354
+
355
+ // Image detection
356
+ function detectImages(next) {
357
+ var images, srcs = {};
358
+
359
+ function imageLoad(image) {
360
+ // Clear src from object and any timers and events associated with the image
361
+ if(image) {
362
+ delete srcs[image.src];
363
+ clearTimeout(self.timers.img[image.src]);
364
+ $(image).unbind(namespace);
365
+ }
366
+
367
+ // If queue is empty after image removal, update tooltip and continue the queue
368
+ if($.isEmptyObject(srcs)) {
369
+ if(reposition !== FALSE) {
370
+ self.reposition(cache.event);
371
+ }
372
+
373
+ next();
374
+ }
375
+ }
376
+
377
+ // Find all content images without dimensions, and if no images were found, continue
378
+ if((images = elem.find('img[src]:not([height]):not([width])')).length === 0) { return imageLoad(); }
379
+
380
+ // Apply timer to each image to poll for dimensions
381
+ images.each(function(i, elem) {
382
+ // Skip if the src is already present
383
+ if(srcs[elem.src] !== undefined) { return; }
384
+
385
+ // Keep track of how many times we poll for image dimensions.
386
+ // If it doesn't return in a reasonable amount of time, it's better
387
+ // to display the tooltip, rather than hold up the queue.
388
+ var iterations = 0, maxIterations = 3;
389
+
390
+ (function timer(){
391
+ // When the dimensions are found, remove the image from the queue
392
+ if(elem.height || elem.width || (iterations > maxIterations)) { return imageLoad(elem); }
393
+
394
+ // Increase iterations and restart timer
395
+ iterations += 1;
396
+ self.timers.img[elem.src] = setTimeout(timer, 700);
397
+ }());
398
+
399
+ // Also apply regular load/error event handlers
400
+ $(elem).bind('error'+namespace+' load'+namespace, function(){ imageLoad(this); });
401
+
402
+ // Store the src and element in our object
403
+ srcs[elem.src] = elem;
404
+ });
405
+ }
406
+
407
+ /*
408
+ * If we're still rendering... insert into 'fx' queue our image dimension
409
+ * checker which will halt the showing of the tooltip until image dimensions
410
+ * can be detected properly.
411
+ */
412
+ if(self.rendered < 0) { tooltip.queue('fx', detectImages); }
413
+
414
+ // We're fully rendered, so reset isDrawing flag and proceed without queue delay
415
+ else { isDrawing = 0; detectImages($.noop); }
416
+
417
+ return self;
418
+ }
419
+
420
+ function assignEvents()
421
+ {
422
+ var posOptions = options.position,
423
+ targets = {
424
+ show: options.show.target,
425
+ hide: options.hide.target,
426
+ viewport: $(posOptions.viewport),
427
+ document: $(document),
428
+ body: $(document.body),
429
+ window: $(window)
430
+ },
431
+ events = {
432
+ show: $.trim('' + options.show.event).split(' '),
433
+ hide: $.trim('' + options.hide.event).split(' ')
434
+ },
435
+ IE6 = $.browser.msie && parseInt($.browser.version, 10) === 6;
436
+
437
+ // Define show event method
438
+ function showMethod(event)
439
+ {
440
+ if(tooltip.hasClass(disabledClass)) { return FALSE; }
441
+
442
+ // Clear hide timers
443
+ clearTimeout(self.timers.show);
444
+ clearTimeout(self.timers.hide);
445
+
446
+ // Start show timer
447
+ var callback = function(){ self.toggle(TRUE, event); };
448
+ if(options.show.delay > 0) {
449
+ self.timers.show = setTimeout(callback, options.show.delay);
450
+ }
451
+ else{ callback(); }
452
+ }
453
+
454
+ // Define hide method
455
+ function hideMethod(event)
456
+ {
457
+ if(tooltip.hasClass(disabledClass) || isPositioning || isDrawing) { return FALSE; }
458
+
459
+ // Check if new target was actually the tooltip element
460
+ var relatedTarget = $(event.relatedTarget || event.target),
461
+ ontoTooltip = relatedTarget.closest(selector)[0] === tooltip[0],
462
+ ontoTarget = relatedTarget[0] === targets.show[0];
463
+
464
+ // Clear timers and stop animation queue
465
+ clearTimeout(self.timers.show);
466
+ clearTimeout(self.timers.hide);
467
+
468
+ // Prevent hiding if tooltip is fixed and event target is the tooltip. Or if mouse positioning is enabled and cursor momentarily overlaps
469
+ if((posOptions.target === 'mouse' && ontoTooltip) || (options.hide.fixed && ((/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)))) {
470
+ try { event.preventDefault(); event.stopImmediatePropagation(); } catch(e) {} return;
471
+ }
472
+
473
+ // If tooltip has displayed, start hide timer
474
+ if(options.hide.delay > 0) {
475
+ self.timers.hide = setTimeout(function(){ self.hide(event); }, options.hide.delay);
476
+ }
477
+ else{ self.hide(event); }
478
+ }
479
+
480
+ // Define inactive method
481
+ function inactiveMethod(event)
482
+ {
483
+ if(tooltip.hasClass(disabledClass)) { return FALSE; }
484
+
485
+ // Clear timer
486
+ clearTimeout(self.timers.inactive);
487
+ self.timers.inactive = setTimeout(function(){ self.hide(event); }, options.hide.inactive);
488
+ }
489
+
490
+ function repositionMethod(event) {
491
+ if(self.rendered && tooltip[0].offsetWidth > 0) { self.reposition(event); }
492
+ }
493
+
494
+ // On mouseenter/mouseleave...
495
+ tooltip.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) {
496
+ var state = event.type === 'mouseenter';
497
+
498
+ // Focus the tooltip on mouseenter (z-index stacking)
499
+ if(state) { self.focus(event); }
500
+
501
+ // Add hover class
502
+ tooltip.toggleClass(hoverClass, state);
503
+ });
504
+
505
+ // If using mouseout/mouseleave as a hide event...
506
+ if(/mouse(out|leave)/i.test(options.hide.event)) {
507
+ // Hide tooltips when leaving current window/frame (but not select/option elements)
508
+ if(options.hide.leave === 'window') {
509
+ targets.window.bind('mouseout'+namespace+' blur'+namespace, function(event) {
510
+ if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) { self.hide(event); }
511
+ });
512
+ }
513
+ }
514
+
515
+ // Enable hide.fixed
516
+ if(options.hide.fixed) {
517
+ // Add tooltip as a hide target
518
+ targets.hide = targets.hide.add(tooltip);
519
+
520
+ // Clear hide timer on tooltip hover to prevent it from closing
521
+ tooltip.bind('mouseover'+namespace, function() {
522
+ if(!tooltip.hasClass(disabledClass)) { clearTimeout(self.timers.hide); }
523
+ });
524
+ }
525
+
526
+ /*
527
+ * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
528
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
529
+ */
530
+ else if(/mouse(over|enter)/i.test(options.show.event)) {
531
+ targets.hide.bind('mouseleave'+namespace, function(event) {
532
+ clearTimeout(self.timers.show);
533
+ });
534
+ }
535
+
536
+ // Hide tooltip on document mousedown if unfocus events are enabled
537
+ if(('' + options.hide.event).indexOf('unfocus') > -1) {
538
+ posOptions.container.closest('html').bind('mousedown'+namespace+' touchstart'+namespace, function(event) {
539
+ var elem = $(event.target),
540
+ enabled = self.rendered && !tooltip.hasClass(disabledClass) && tooltip[0].offsetWidth > 0,
541
+ isAncestor = elem.parents(selector).filter(tooltip[0]).length > 0;
542
+
543
+ if(elem[0] !== target[0] && elem[0] !== tooltip[0] && !isAncestor &&
544
+ !target.has(elem[0]).length && !elem.attr('disabled')
545
+ ) {
546
+ self.hide(event);
547
+ }
548
+ });
549
+ }
550
+
551
+ // Check if the tooltip hides when inactive
552
+ if('number' === typeof options.hide.inactive) {
553
+ // Bind inactive method to target as a custom event
554
+ targets.show.bind('qtip-'+id+'-inactive', inactiveMethod);
555
+
556
+ // Define events which reset the 'inactive' event handler
557
+ $.each(QTIP.inactiveEvents, function(index, type){
558
+ targets.hide.add(elements.tooltip).bind(type+namespace+'-inactive', inactiveMethod);
559
+ });
560
+ }
561
+
562
+ // Apply hide events
563
+ $.each(events.hide, function(index, type) {
564
+ var showIndex = $.inArray(type, events.show),
565
+ targetHide = $(targets.hide);
566
+
567
+ // Both events and targets are identical, apply events using a toggle
568
+ if((showIndex > -1 && targetHide.add(targets.show).length === targetHide.length) || type === 'unfocus')
569
+ {
570
+ targets.show.bind(type+namespace, function(event) {
571
+ if(tooltip[0].offsetWidth > 0) { hideMethod(event); }
572
+ else { showMethod(event); }
573
+ });
574
+
575
+ // Don't bind the event again
576
+ delete events.show[ showIndex ];
577
+ }
578
+
579
+ // Events are not identical, bind normally
580
+ else { targets.hide.bind(type+namespace, hideMethod); }
581
+ });
582
+
583
+ // Apply show events
584
+ $.each(events.show, function(index, type) {
585
+ targets.show.bind(type+namespace, showMethod);
586
+ });
587
+
588
+ // Check if the tooltip hides when mouse is moved a certain distance
589
+ if('number' === typeof options.hide.distance) {
590
+ // Bind mousemove to target to detect distance difference
591
+ targets.show.add(tooltip).bind('mousemove'+namespace, function(event) {
592
+ var origin = cache.origin || {},
593
+ limit = options.hide.distance,
594
+ abs = Math.abs;
595
+
596
+ // Check if the movement has gone beyond the limit, and hide it if so
597
+ if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
598
+ self.hide(event);
599
+ }
600
+ });
601
+ }
602
+
603
+ // Mouse positioning events
604
+ if(posOptions.target === 'mouse') {
605
+ // Cache mousemove coords on show targets
606
+ targets.show.bind('mousemove'+namespace, storeMouse);
607
+
608
+ // If mouse adjustment is on...
609
+ if(posOptions.adjust.mouse) {
610
+ // Apply a mouseleave event so we don't get problems with overlapping
611
+ if(options.hide.event) {
612
+ // Hide when we leave the tooltip and not onto the show target
613
+ tooltip.bind('mouseleave'+namespace, function(event) {
614
+ if((event.relatedTarget || event.target) !== targets.show[0]) { self.hide(event); }
615
+ });
616
+
617
+ // Track if we're on the target or not
618
+ elements.target.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) {
619
+ cache.onTarget = event.type === 'mouseenter';
620
+ });
621
+ }
622
+
623
+ // Update tooltip position on mousemove
624
+ targets.document.bind('mousemove'+namespace, function(event) {
625
+ // Update the tooltip position only if the tooltip is visible and adjustment is enabled
626
+ if(self.rendered && cache.onTarget && !tooltip.hasClass(disabledClass) && tooltip[0].offsetWidth > 0) {
627
+ self.reposition(event || MOUSE);
628
+ }
629
+ });
630
+ }
631
+ }
632
+
633
+ // Adjust positions of the tooltip on window resize if enabled
634
+ if(posOptions.adjust.resize || targets.viewport.length) {
635
+ ($.event.special.resize ? targets.viewport : targets.window).bind('resize'+namespace, repositionMethod);
636
+ }
637
+
638
+ // Adjust tooltip position on scroll of the window or viewport element if present
639
+ targets.window.bind('scroll'+namespace, repositionMethod);
640
+ }
641
+
642
+ function unassignEvents()
643
+ {
644
+ var targets = [
645
+ options.show.target[0],
646
+ options.hide.target[0],
647
+ self.rendered && elements.tooltip[0],
648
+ options.position.container[0],
649
+ options.position.viewport[0],
650
+ options.position.container.closest('html')[0], // unfocus
651
+ window,
652
+ document
653
+ ];
654
+
655
+ // Check if tooltip is rendered
656
+ if(self.rendered) {
657
+ $([]).pushStack( $.grep(targets, function(i){ return typeof i === 'object'; }) ).unbind(namespace);
658
+ }
659
+
660
+ // Tooltip isn't yet rendered, remove render event
661
+ else { options.show.target.unbind(namespace+'-create'); }
662
+ }
663
+
664
+ // Setup builtin .set() option checks
665
+ self.checks.builtin = {
666
+ // Core checks
667
+ '^id$': function(obj, o, v) {
668
+ var id = v === TRUE ? QTIP.nextid : v,
669
+ tooltipID = NAMESPACE + '-' + id;
670
+
671
+ if(id !== FALSE && id.length > 0 && !$('#'+tooltipID).length) {
672
+ tooltip[0].id = tooltipID;
673
+ elements.content[0].id = tooltipID + '-content';
674
+ elements.title[0].id = tooltipID + '-title';
675
+ }
676
+ },
677
+
678
+ // Content checks
679
+ '^content.text$': function(obj, o, v) { updateContent(options.content.text); },
680
+ '^content.deferred$': function(obj, o, v) { deferredContent(options.content.deferred); },
681
+ '^content.title.text$': function(obj, o, v) {
682
+ // Remove title if content is null
683
+ if(!v) { return removeTitle(); }
684
+
685
+ // If title isn't already created, create it now and update
686
+ if(!elements.title && v) { createTitle(); }
687
+ updateTitle(v);
688
+ },
689
+ '^content.title.button$': function(obj, o, v){ updateButton(v); },
690
+
691
+ // Position checks
692
+ '^position.(my|at)$': function(obj, o, v){
693
+ // Parse new corner value into Corner objecct
694
+ if('string' === typeof v) {
695
+ obj[o] = new PLUGINS.Corner(v);
696
+ }
697
+ },
698
+ '^position.container$': function(obj, o, v){
699
+ if(self.rendered) { tooltip.appendTo(v); }
700
+ },
701
+
702
+ // Show checks
703
+ '^show.ready$': function() {
704
+ if(!self.rendered) { self.render(1); }
705
+ else { self.toggle(TRUE); }
706
+ },
707
+
708
+ // Style checks
709
+ '^style.classes$': function(obj, o, v) {
710
+ tooltip.attr('class', NAMESPACE + ' qtip ' + v);
711
+ },
712
+ '^style.width|height': function(obj, o, v) {
713
+ tooltip.css(o, v);
714
+ },
715
+ '^style.widget|content.title': setWidget,
716
+
717
+ // Events check
718
+ '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
719
+ tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
720
+ },
721
+
722
+ // Properties which require event reassignment
723
+ '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
724
+ var posOptions = options.position;
725
+
726
+ // Set tracking flag
727
+ tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
728
+
729
+ // Reassign events
730
+ unassignEvents(); assignEvents();
731
+ }
732
+ };
733
+
734
+ $.extend(self, {
735
+ /*
736
+ * Psuedo-private API methods
737
+ */
738
+ _triggerEvent: function(type, args, event)
739
+ {
740
+ var callback = $.Event('tooltip'+type);
741
+ callback.originalEvent = (event ? $.extend({}, event) : NULL) || cache.event || NULL;
742
+ tooltip.trigger(callback, [self].concat(args || []));
743
+
744
+ return !callback.isDefaultPrevented();
745
+ },
746
+
747
+ /*
748
+ * Public API methods
749
+ */
750
+ render: function(show)
751
+ {
752
+ if(self.rendered) { return self; } // If tooltip has already been rendered, exit
753
+
754
+ var text = options.content.text,
755
+ title = options.content.title,
756
+ posOptions = options.position;
757
+
758
+ // Add ARIA attributes to target
759
+ $.attr(target[0], 'aria-describedby', tooltipID);
760
+
761
+ // Create tooltip element
762
+ tooltip = elements.tooltip = $('<div/>', {
763
+ 'id': tooltipID,
764
+ 'class': [ NAMESPACE, defaultClass, options.style.classes, NAMESPACE + '-pos-' + options.position.my.abbrev() ].join(' '),
765
+ 'width': options.style.width || '',
766
+ 'height': options.style.height || '',
767
+ 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
768
+
769
+ /* ARIA specific attributes */
770
+ 'role': 'alert',
771
+ 'aria-live': 'polite',
772
+ 'aria-atomic': FALSE,
773
+ 'aria-describedby': tooltipID + '-content',
774
+ 'aria-hidden': TRUE
775
+ })
776
+ .toggleClass(disabledClass, cache.disabled)
777
+ .data('qtip', self)
778
+ .appendTo(options.position.container)
779
+ .append(
780
+ // Create content element
781
+ elements.content = $('<div />', {
782
+ 'class': NAMESPACE + '-content',
783
+ 'id': tooltipID + '-content',
784
+ 'aria-atomic': TRUE
785
+ })
786
+ );
787
+
788
+ // Set rendered flag and prevent redundant reposition calls for now
789
+ self.rendered = -1;
790
+ isPositioning = 1;
791
+
792
+ // Create title...
793
+ if(title.text) {
794
+ createTitle();
795
+
796
+ // Update title only if its not a callback (called in toggle if so)
797
+ if(!$.isFunction(title.text)) { updateTitle(title.text, FALSE); }
798
+ }
799
+
800
+ // Create button
801
+ else if(title.button) { createButton(); }
802
+
803
+ // Set proper rendered flag and update content if not a callback function (called in toggle)
804
+ if(!$.isFunction(text) || text.then) { updateContent(text, FALSE); }
805
+ self.rendered = TRUE;
806
+
807
+ // Setup widget classes
808
+ setWidget();
809
+
810
+ // Assign passed event callbacks (before plugins!)
811
+ $.each(options.events, function(name, callback) {
812
+ if($.isFunction(callback)) {
813
+ tooltip.bind(name === 'toggle' ? 'tooltipshow tooltiphide' : 'tooltip'+name, callback);
814
+ }
815
+ });
816
+
817
+ // Initialize 'render' plugins
818
+ $.each(PLUGINS, function() {
819
+ if(this.initialize === 'render') { this(self); }
820
+ });
821
+
822
+ // Assign events
823
+ assignEvents();
824
+
825
+ /* Queue this part of the render process in our fx queue so we can
826
+ * load images before the tooltip renders fully.
827
+ *
828
+ * See: updateContent method
829
+ */
830
+ tooltip.queue('fx', function(next) {
831
+ // tooltiprender event
832
+ self._triggerEvent('render');
833
+
834
+ // Reset flags
835
+ isPositioning = 0;
836
+
837
+ // Show tooltip if needed
838
+ if(options.show.ready || show) {
839
+ self.toggle(TRUE, cache.event, FALSE);
840
+ }
841
+
842
+ next(); // Move on to next method in queue
843
+ });
844
+
845
+ return self;
846
+ },
847
+
848
+ get: function(notation)
849
+ {
850
+ var result, o;
851
+
852
+ switch(notation.toLowerCase())
853
+ {
854
+ case 'dimensions':
855
+ result = {
856
+ height: tooltip.outerHeight(FALSE),
857
+ width: tooltip.outerWidth(FALSE)
858
+ };
859
+ break;
860
+
861
+ case 'offset':
862
+ result = PLUGINS.offset(tooltip, options.position.container);
863
+ break;
864
+
865
+ default:
866
+ o = convertNotation(notation.toLowerCase());
867
+ result = o[0][ o[1] ];
868
+ result = result.precedance ? result.string() : result;
869
+ break;
870
+ }
871
+
872
+ return result;
873
+ },
874
+
875
+ set: function(option, value)
876
+ {
877
+ var rmove = /^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,
878
+ rdraw = /^content\.(title|attr)|style/i,
879
+ reposition = FALSE,
880
+ checks = self.checks,
881
+ name;
882
+
883
+ function callback(notation, args) {
884
+ var category, rule, match;
885
+
886
+ for(category in checks) {
887
+ for(rule in checks[category]) {
888
+ if(match = (new RegExp(rule, 'i')).exec(notation)) {
889
+ args.push(match);
890
+ checks[category][rule].apply(self, args);
891
+ }
892
+ }
893
+ }
894
+ }
895
+
896
+ // Convert singular option/value pair into object form
897
+ if('string' === typeof option) {
898
+ name = option; option = {}; option[name] = value;
899
+ }
900
+ else { option = $.extend(TRUE, {}, option); }
901
+
902
+ // Set all of the defined options to their new values
903
+ $.each(option, function(notation, value) {
904
+ var obj = convertNotation( notation.toLowerCase() ), previous;
905
+
906
+ // Set new obj value
907
+ previous = obj[0][ obj[1] ];
908
+ obj[0][ obj[1] ] = 'object' === typeof value && value.nodeType ? $(value) : value;
909
+
910
+ // Set the new params for the callback
911
+ option[notation] = [obj[0], obj[1], value, previous];
912
+
913
+ // Also check if we need to reposition
914
+ reposition = rmove.test(notation) || reposition;
915
+ });
916
+
917
+ // Re-sanitize options
918
+ sanitizeOptions(options);
919
+
920
+ /*
921
+ * Execute any valid callbacks for the set options
922
+ * Also set isPositioning/isDrawing so we don't get loads of redundant repositioning calls.
923
+ */
924
+ isPositioning = 1; $.each(option, callback); isPositioning = 0;
925
+
926
+ // Update position if needed
927
+ if(self.rendered && tooltip[0].offsetWidth > 0 && reposition) {
928
+ self.reposition( options.position.target === 'mouse' ? NULL : cache.event );
929
+ }
930
+
931
+ return self;
932
+ },
933
+
934
+ toggle: function(state, event)
935
+ {
936
+ // Try to prevent flickering when tooltip overlaps show element
937
+ if(event) {
938
+ if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) &&
939
+ options.show.target.add(event.target).length === options.show.target.length &&
940
+ tooltip.has(event.relatedTarget).length) {
941
+ return self;
942
+ }
943
+
944
+ // Cache event
945
+ cache.event = $.extend({}, event);
946
+ }
947
+
948
+ // Render the tooltip if showing and it isn't already
949
+ if(!self.rendered) { return state ? self.render(1) : self; }
950
+
951
+ var type = state ? 'show' : 'hide',
952
+ opts = options[type],
953
+ otherOpts = options[ !state ? 'show' : 'hide' ],
954
+ posOptions = options.position,
955
+ contentOptions = options.content,
956
+ visible = tooltip[0].offsetWidth > 0,
957
+ animate = state || opts.target.length === 1,
958
+ sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
959
+ showEvent, delay;
960
+
961
+ // Detect state if valid one isn't provided
962
+ if((typeof state).search('boolean|number')) { state = !visible; }
963
+
964
+ // Return if element is already in correct state
965
+ if(!tooltip.is(':animated') && visible === state && sameTarget) { return self; }
966
+
967
+ // tooltipshow/tooltiphide events
968
+ if(!self._triggerEvent(type, [90])) { return self; }
969
+
970
+ // Set ARIA hidden status attribute
971
+ $.attr(tooltip[0], 'aria-hidden', !!!state);
972
+
973
+ // Execute state specific properties
974
+ if(state) {
975
+ // Store show origin coordinates
976
+ cache.origin = $.extend({}, MOUSE);
977
+
978
+ // Focus the tooltip
979
+ self.focus(event);
980
+
981
+ // Update tooltip content & title if it's a dynamic function
982
+ if($.isFunction(contentOptions.text)) { updateContent(contentOptions.text, FALSE); }
983
+ if($.isFunction(contentOptions.title.text)) { updateTitle(contentOptions.title.text, FALSE); }
984
+
985
+ // Cache mousemove events for positioning purposes (if not already tracking)
986
+ if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
987
+ $(document).bind('mousemove.qtip', storeMouse);
988
+ trackingBound = TRUE;
989
+ }
990
+
991
+ // Update the tooltip position
992
+ self.reposition(event, arguments[2]);
993
+
994
+ // Hide other tooltips if tooltip is solo
995
+ if(!!opts.solo) {
996
+ $(selector, opts.solo).not(tooltip).qtip('hide', $.Event('tooltipsolo'));
997
+ }
998
+ }
999
+ else {
1000
+ // Clear show timer if we're hiding
1001
+ clearTimeout(self.timers.show);
1002
+
1003
+ // Remove cached origin on hide
1004
+ delete cache.origin;
1005
+
1006
+ // Remove mouse tracking event if not needed (all tracking qTips are hidden)
1007
+ if(trackingBound && !$(selector+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
1008
+ $(document).unbind('mousemove.qtip');
1009
+ trackingBound = FALSE;
1010
+ }
1011
+
1012
+ // Blur the tooltip
1013
+ self.blur(event);
1014
+ }
1015
+
1016
+ // Define post-animation, state specific properties
1017
+ function after() {
1018
+ if(state) {
1019
+ // Prevent antialias from disappearing in IE by removing filter
1020
+ if($.browser.msie) { tooltip[0].style.removeAttribute('filter'); }
1021
+
1022
+ // Remove overflow setting to prevent tip bugs
1023
+ tooltip.css('overflow', '');
1024
+
1025
+ // Autofocus elements if enabled
1026
+ if('string' === typeof opts.autofocus) {
1027
+ $(opts.autofocus, tooltip).focus();
1028
+ }
1029
+
1030
+ // If set, hide tooltip when inactive for delay period
1031
+ opts.target.trigger('qtip-'+id+'-inactive');
1032
+ }
1033
+ else {
1034
+ // Reset CSS states
1035
+ tooltip.css({
1036
+ display: '',
1037
+ visibility: '',
1038
+ opacity: '',
1039
+ left: '',
1040
+ top: ''
1041
+ });
1042
+ }
1043
+
1044
+ // tooltipvisible/tooltiphidden events
1045
+ self._triggerEvent(state ? 'visible' : 'hidden');
1046
+ }
1047
+
1048
+ // If no effect type is supplied, use a simple toggle
1049
+ if(opts.effect === FALSE || animate === FALSE) {
1050
+ tooltip[ type ]();
1051
+ after.call(tooltip);
1052
+ }
1053
+
1054
+ // Use custom function if provided
1055
+ else if($.isFunction(opts.effect)) {
1056
+ tooltip.stop(1, 1);
1057
+ opts.effect.call(tooltip, self);
1058
+ tooltip.queue('fx', function(n){ after(); n(); });
1059
+ }
1060
+
1061
+ // Use basic fade function by default
1062
+ else { tooltip.fadeTo(90, state ? 1 : 0, after); }
1063
+
1064
+ // If inactive hide method is set, active it
1065
+ if(state) { opts.target.trigger('qtip-'+id+'-inactive'); }
1066
+
1067
+ return self;
1068
+ },
1069
+
1070
+ show: function(event){ return self.toggle(TRUE, event); },
1071
+
1072
+ hide: function(event){ return self.toggle(FALSE, event); },
1073
+
1074
+ focus: function(event)
1075
+ {
1076
+ if(!self.rendered) { return self; }
1077
+
1078
+ var qtips = $(selector),
1079
+ curIndex = parseInt(tooltip[0].style.zIndex, 10),
1080
+ newIndex = QTIP.zindex + qtips.length,
1081
+ cachedEvent = $.extend({}, event),
1082
+ focusedElem;
1083
+
1084
+ // Only update the z-index if it has changed and tooltip is not already focused
1085
+ if(!tooltip.hasClass(focusClass))
1086
+ {
1087
+ // tooltipfocus event
1088
+ if(self._triggerEvent('focus', [newIndex], cachedEvent)) {
1089
+ // Only update z-index's if they've changed
1090
+ if(curIndex !== newIndex) {
1091
+ // Reduce our z-index's and keep them properly ordered
1092
+ qtips.each(function() {
1093
+ if(this.style.zIndex > curIndex) {
1094
+ this.style.zIndex = this.style.zIndex - 1;
1095
+ }
1096
+ });
1097
+
1098
+ // Fire blur event for focused tooltip
1099
+ qtips.filter('.' + focusClass).qtip('blur', cachedEvent);
1100
+ }
1101
+
1102
+ // Set the new z-index
1103
+ tooltip.addClass(focusClass)[0].style.zIndex = newIndex;
1104
+ }
1105
+ }
1106
+
1107
+ return self;
1108
+ },
1109
+
1110
+ blur: function(event) {
1111
+ // Set focused status to FALSE
1112
+ tooltip.removeClass(focusClass);
1113
+
1114
+ // tooltipblur event
1115
+ self._triggerEvent('blur', [tooltip.css('zIndex')], event);
1116
+
1117
+ return self;
1118
+ },
1119
+
1120
+ reposition: function(event, effect)
1121
+ {
1122
+ if(!self.rendered || isPositioning) { return self; }
1123
+
1124
+ // Set positioning flag
1125
+ isPositioning = 1;
1126
+
1127
+ var target = options.position.target,
1128
+ posOptions = options.position,
1129
+ my = posOptions.my,
1130
+ at = posOptions.at,
1131
+ adjust = posOptions.adjust,
1132
+ method = adjust.method.split(' '),
1133
+ elemWidth = tooltip.outerWidth(FALSE),
1134
+ elemHeight = tooltip.outerHeight(FALSE),
1135
+ targetWidth = 0,
1136
+ targetHeight = 0,
1137
+ type = tooltip.css('position'),
1138
+ viewport = posOptions.viewport,
1139
+ position = { left: 0, top: 0 },
1140
+ container = posOptions.container,
1141
+ visible = tooltip[0].offsetWidth > 0,
1142
+ isScroll = event && event.type === 'scroll',
1143
+ win = $(window),
1144
+ adjusted, offset;
1145
+
1146
+ // Check if absolute position was passed
1147
+ if($.isArray(target) && target.length === 2) {
1148
+ // Force left top and set position
1149
+ at = { x: LEFT, y: TOP };
1150
+ position = { left: target[0], top: target[1] };
1151
+ }
1152
+
1153
+ // Check if mouse was the target
1154
+ else if(target === 'mouse' && ((event && event.pageX) || cache.event.pageX)) {
1155
+ // Force left top to allow flipping
1156
+ at = { x: LEFT, y: TOP };
1157
+
1158
+ // Use cached event if one isn't available for positioning
1159
+ event = MOUSE && MOUSE.pageX && (adjust.mouse || !event || !event.pageX) ? { pageX: MOUSE.pageX, pageY: MOUSE.pageY } :
1160
+ (event && (event.type === 'resize' || event.type === 'scroll') ? cache.event :
1161
+ event && event.pageX && event.type === 'mousemove' ? event :
1162
+ !adjust.mouse && cache.origin && cache.origin.pageX && options.show.distance ? cache.origin :
1163
+ event) || event || cache.event || MOUSE || {};
1164
+
1165
+ // Use event coordinates for position
1166
+ if(type !== 'static') { position = container.offset(); }
1167
+ position = { left: event.pageX - position.left, top: event.pageY - position.top };
1168
+
1169
+ // Scroll events are a pain, some browsers
1170
+ if(adjust.mouse && isScroll) {
1171
+ position.left -= MOUSE.scrollX - win.scrollLeft();
1172
+ position.top -= MOUSE.scrollY - win.scrollTop();
1173
+ }
1174
+ }
1175
+
1176
+ // Target wasn't mouse or absolute...
1177
+ else {
1178
+ // Check if event targetting is being used
1179
+ if(target === 'event' && event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
1180
+ cache.target = $(event.target);
1181
+ }
1182
+ else if(target !== 'event'){
1183
+ cache.target = $(target.jquery ? target : elements.target);
1184
+ }
1185
+ target = cache.target;
1186
+
1187
+ // Parse the target into a jQuery object and make sure there's an element present
1188
+ target = $(target).eq(0);
1189
+ if(target.length === 0) { return self; }
1190
+
1191
+ // Check if window or document is the target
1192
+ else if(target[0] === document || target[0] === window) {
1193
+ targetWidth = PLUGINS.iOS ? window.innerWidth : target.width();
1194
+ targetHeight = PLUGINS.iOS ? window.innerHeight : target.height();
1195
+
1196
+ if(target[0] === window) {
1197
+ position = {
1198
+ top: (viewport || target).scrollTop(),
1199
+ left: (viewport || target).scrollLeft()
1200
+ };
1201
+ }
1202
+ }
1203
+
1204
+ // Use Imagemap/SVG plugins if needed
1205
+ else if(PLUGINS.imagemap && target.is('area')) {
1206
+ adjusted = PLUGINS.imagemap(self, target, at, PLUGINS.viewport ? method : FALSE);
1207
+ }
1208
+ else if(PLUGINS.svg && target[0].ownerSVGElement) {
1209
+ adjusted = PLUGINS.svg(self, target, at, PLUGINS.viewport ? method : FALSE);
1210
+ }
1211
+
1212
+ else {
1213
+ targetWidth = target.outerWidth(FALSE);
1214
+ targetHeight = target.outerHeight(FALSE);
1215
+
1216
+ position = PLUGINS.offset(target, container);
1217
+ }
1218
+
1219
+ // Parse returned plugin values into proper variables
1220
+ if(adjusted) {
1221
+ targetWidth = adjusted.width;
1222
+ targetHeight = adjusted.height;
1223
+ offset = adjusted.offset;
1224
+ position = adjusted.position;
1225
+ }
1226
+
1227
+ // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
1228
+ if((PLUGINS.iOS > 3.1 && PLUGINS.iOS < 4.1) ||
1229
+ (PLUGINS.iOS >= 4.3 && PLUGINS.iOS < 4.33) ||
1230
+ (!PLUGINS.iOS && type === 'fixed')
1231
+ ){
1232
+ position.left -= win.scrollLeft();
1233
+ position.top -= win.scrollTop();
1234
+ }
1235
+
1236
+ // Adjust position relative to target
1237
+ position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;
1238
+ position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;
1239
+ }
1240
+
1241
+ // Adjust position relative to tooltip
1242
+ position.left += adjust.x + (my.x === RIGHT ? -elemWidth : my.x === CENTER ? -elemWidth / 2 : 0);
1243
+ position.top += adjust.y + (my.y === BOTTOM ? -elemHeight : my.y === CENTER ? -elemHeight / 2 : 0);
1244
+
1245
+ // Use viewport adjustment plugin if enabled
1246
+ if(PLUGINS.viewport) {
1247
+ position.adjusted = PLUGINS.viewport(
1248
+ self, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight
1249
+ );
1250
+
1251
+ // Apply offsets supplied by positioning plugin (if used)
1252
+ if(offset && position.adjusted.left) { position.left += offset.left; }
1253
+ if(offset && position.adjusted.top) { position.top += offset.top; }
1254
+ }
1255
+
1256
+ // Viewport adjustment is disabled, set values to zero
1257
+ else { position.adjusted = { left: 0, top: 0 }; }
1258
+
1259
+ // tooltipmove event
1260
+ if(!self._triggerEvent('move', [position, viewport.elem || viewport], event)) { return self; }
1261
+ delete position.adjusted;
1262
+
1263
+ // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
1264
+ if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
1265
+ tooltip.css(position);
1266
+ }
1267
+
1268
+ // Use custom function if provided
1269
+ else if($.isFunction(posOptions.effect)) {
1270
+ posOptions.effect.call(tooltip, self, $.extend({}, position));
1271
+ tooltip.queue(function(next) {
1272
+ // Reset attributes to avoid cross-browser rendering bugs
1273
+ $(this).css({ opacity: '', height: '' });
1274
+ if($.browser.msie) { this.style.removeAttribute('filter'); }
1275
+
1276
+ next();
1277
+ });
1278
+ }
1279
+
1280
+ // Set positioning flagwtf
1281
+ isPositioning = 0;
1282
+
1283
+ return self;
1284
+ },
1285
+
1286
+ disable: function(state)
1287
+ {
1288
+ if('boolean' !== typeof state) {
1289
+ state = !(tooltip.hasClass(disabledClass) || cache.disabled);
1290
+ }
1291
+
1292
+ if(self.rendered) {
1293
+ tooltip.toggleClass(disabledClass, state);
1294
+ $.attr(tooltip[0], 'aria-disabled', state);
1295
+ }
1296
+ else {
1297
+ cache.disabled = !!state;
1298
+ }
1299
+
1300
+ return self;
1301
+ },
1302
+
1303
+ enable: function() { return self.disable(FALSE); },
1304
+
1305
+ destroy: function()
1306
+ {
1307
+ var t = target[0],
1308
+ title = $.attr(t, oldtitle),
1309
+ elemAPI = target.data('qtip');
1310
+
1311
+ // Set flag the signify destroy is taking place to plugins
1312
+ self.destroyed = TRUE;
1313
+
1314
+ // Destroy tooltip and any associated plugins if rendered
1315
+ if(self.rendered) {
1316
+ tooltip.stop(1,0).remove();
1317
+
1318
+ $.each(self.plugins, function() {
1319
+ if(this.destroy) { this.destroy(); }
1320
+ });
1321
+ }
1322
+
1323
+ // Clear timers and remove bound events
1324
+ clearTimeout(self.timers.show);
1325
+ clearTimeout(self.timers.hide);
1326
+ unassignEvents();
1327
+
1328
+ // If the API if actually this qTip API...
1329
+ if(!elemAPI || self === elemAPI) {
1330
+ // Remove api object
1331
+ $.removeData(t, 'qtip');
1332
+
1333
+ // Reset old title attribute if removed
1334
+ if(options.suppress && title) {
1335
+ $.attr(t, 'title', title);
1336
+ target.removeAttr(oldtitle);
1337
+ }
1338
+
1339
+ // Remove ARIA attributes
1340
+ target.removeAttr('aria-describedby');
1341
+ }
1342
+
1343
+ // Remove qTip events associated with this API
1344
+ target.unbind('.qtip-'+id);
1345
+
1346
+ // Remove ID from sued id object
1347
+ delete usedIDs[self.id];
1348
+
1349
+ return target;
1350
+ }
1351
+ });
1352
+ }
1353
+
1354
+ // Initialization method
1355
+ function init(id, opts)
1356
+ {
1357
+ var obj, posOptions, attr, config, title,
1358
+
1359
+ // Setup element references
1360
+ elem = $(this),
1361
+ docBody = $(document.body),
1362
+
1363
+ // Use document body instead of document element if needed
1364
+ newTarget = this === document ? docBody : elem,
1365
+
1366
+ // Grab metadata from element if plugin is present
1367
+ metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,
1368
+
1369
+ // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
1370
+ metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
1371
+
1372
+ // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
1373
+ html5 = elem.data(opts.metadata.name || 'qtipopts');
1374
+
1375
+ // If we don't get an object returned attempt to parse it manualyl without parseJSON
1376
+ try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } catch(e) {}
1377
+
1378
+ // Merge in and sanitize metadata
1379
+ config = $.extend(TRUE, {}, QTIP.defaults, opts,
1380
+ typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
1381
+ sanitizeOptions(metadata5 || metadata));
1382
+
1383
+ // Re-grab our positioning options now we've merged our metadata and set id to passed value
1384
+ posOptions = config.position;
1385
+ config.id = id;
1386
+
1387
+ // Setup missing content if none is detected
1388
+ if('boolean' === typeof config.content.text) {
1389
+ attr = elem.attr(config.content.attr);
1390
+
1391
+ // Grab from supplied attribute if available
1392
+ if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
1393
+
1394
+ // No valid content was found, abort render
1395
+ else { return FALSE; }
1396
+ }
1397
+
1398
+ // Setup target options
1399
+ if(!posOptions.container.length) { posOptions.container = docBody; }
1400
+ if(posOptions.target === FALSE) { posOptions.target = newTarget; }
1401
+ if(config.show.target === FALSE) { config.show.target = newTarget; }
1402
+ if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
1403
+ if(config.hide.target === FALSE) { config.hide.target = newTarget; }
1404
+ if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
1405
+
1406
+ // Ensure we only use a single container
1407
+ posOptions.container = posOptions.container.eq(0);
1408
+
1409
+ // Convert position corner values into x and y strings
1410
+ posOptions.at = new PLUGINS.Corner(posOptions.at);
1411
+ posOptions.my = new PLUGINS.Corner(posOptions.my);
1412
+
1413
+ // Destroy previous tooltip if overwrite is enabled, or skip element if not
1414
+ if($.data(this, 'qtip')) {
1415
+ if(config.overwrite) {
1416
+ elem.qtip('destroy');
1417
+ }
1418
+ else if(config.overwrite === FALSE) {
1419
+ return FALSE;
1420
+ }
1421
+ }
1422
+
1423
+ // Remove title attribute and store it if present
1424
+ if(config.suppress && (title = $.attr(this, 'title'))) {
1425
+ // Final attr call fixes event delegatiom and IE default tooltip showing problem
1426
+ $(this).removeAttr('title').attr(oldtitle, title).attr('title', '');
1427
+ }
1428
+
1429
+ // Initialize the tooltip and add API reference
1430
+ obj = new QTip(elem, config, id, !!attr);
1431
+ $.data(this, 'qtip', obj);
1432
+
1433
+ // Catch remove/removeqtip events on target element to destroy redundant tooltip
1434
+ elem.bind('remove.qtip-'+id+' removeqtip.qtip-'+id, function(){ obj.destroy(); });
1435
+
1436
+ return obj;
1437
+ }
1438
+
1439
+ // jQuery $.fn extension method
1440
+ QTIP = $.fn.qtip = function(options, notation, newValue)
1441
+ {
1442
+ var command = ('' + options).toLowerCase(), // Parse command
1443
+ returned = NULL,
1444
+ args = $.makeArray(arguments).slice(1),
1445
+ event = args[args.length - 1],
1446
+ opts = this[0] ? $.data(this[0], 'qtip') : NULL;
1447
+
1448
+ // Check for API request
1449
+ if((!arguments.length && opts) || command === 'api') {
1450
+ return opts;
1451
+ }
1452
+
1453
+ // Execute API command if present
1454
+ else if('string' === typeof options)
1455
+ {
1456
+ this.each(function()
1457
+ {
1458
+ var api = $.data(this, 'qtip');
1459
+ if(!api) { return TRUE; }
1460
+
1461
+ // Cache the event if possible
1462
+ if(event && event.timeStamp) { api.cache.event = event; }
1463
+
1464
+ // Check for specific API commands
1465
+ if((command === 'option' || command === 'options') && notation) {
1466
+ if($.isPlainObject(notation) || newValue !== undefined) {
1467
+ api.set(notation, newValue);
1468
+ }
1469
+ else {
1470
+ returned = api.get(notation);
1471
+ return FALSE;
1472
+ }
1473
+ }
1474
+
1475
+ // Execute API command
1476
+ else if(api[command]) {
1477
+ api[command].apply(api[command], args);
1478
+ }
1479
+ });
1480
+
1481
+ return returned !== NULL ? returned : this;
1482
+ }
1483
+
1484
+ // No API commands. validate provided options and setup qTips
1485
+ else if('object' === typeof options || !arguments.length)
1486
+ {
1487
+ opts = sanitizeOptions($.extend(TRUE, {}, options));
1488
+
1489
+ // Bind the qTips
1490
+ return QTIP.bind.call(this, opts, event);
1491
+ }
1492
+ };
1493
+
1494
+ // $.fn.qtip Bind method
1495
+ QTIP.bind = function(opts, event)
1496
+ {
1497
+ return this.each(function(i) {
1498
+ var options, targets, events, namespace, api, id;
1499
+
1500
+ // Find next available ID, or use custom ID if provided
1501
+ id = $.isArray(opts.id) ? opts.id[i] : opts.id;
1502
+ id = !id || id === FALSE || id.length < 1 || usedIDs[id] ? QTIP.nextid++ : (usedIDs[id] = id);
1503
+
1504
+ // Setup events namespace
1505
+ namespace = '.qtip-'+id+'-create';
1506
+
1507
+ // Initialize the qTip and re-grab newly sanitized options
1508
+ api = init.call(this, id, opts);
1509
+ if(api === FALSE) { return TRUE; }
1510
+ options = api.options;
1511
+
1512
+ // Initialize plugins
1513
+ $.each(PLUGINS, function() {
1514
+ if(this.initialize === 'initialize') { this(api); }
1515
+ });
1516
+
1517
+ // Determine hide and show targets
1518
+ targets = { show: options.show.target, hide: options.hide.target };
1519
+ events = {
1520
+ show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace,
1521
+ hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace
1522
+ };
1523
+
1524
+ /*
1525
+ * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1526
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1527
+ */
1528
+ if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) {
1529
+ events.hide += ' mouseleave' + namespace;
1530
+ }
1531
+
1532
+ /*
1533
+ * Also make sure initial mouse targetting works correctly by caching mousemove coords
1534
+ * on show targets before the tooltip has rendered.
1535
+ *
1536
+ * Also set onTarget when triggered to keep mouse tracking working
1537
+ */
1538
+ targets.show.bind('mousemove'+namespace, function(event) {
1539
+ storeMouse(event);
1540
+ api.cache.onTarget = TRUE;
1541
+ });
1542
+
1543
+ // Define hoverIntent function
1544
+ function hoverIntent(event) {
1545
+ function render() {
1546
+ // Cache mouse coords,render and render the tooltip
1547
+ api.render(typeof event === 'object' || options.show.ready);
1548
+
1549
+ // Unbind show and hide events
1550
+ targets.show.add(targets.hide).unbind(namespace);
1551
+ }
1552
+
1553
+ // Only continue if tooltip isn't disabled
1554
+ if(api.cache.disabled) { return FALSE; }
1555
+
1556
+ // Cache the event data
1557
+ api.cache.event = $.extend({}, event);
1558
+ api.cache.target = event ? $(event.target) : [undefined];
1559
+
1560
+ // Start the event sequence
1561
+ if(options.show.delay > 0) {
1562
+ clearTimeout(api.timers.show);
1563
+ api.timers.show = setTimeout(render, options.show.delay);
1564
+ if(events.show !== events.hide) {
1565
+ targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); });
1566
+ }
1567
+ }
1568
+ else { render(); }
1569
+ }
1570
+
1571
+ // Bind show events to target
1572
+ targets.show.bind(events.show, hoverIntent);
1573
+
1574
+ // Prerendering is enabled, create tooltip now
1575
+ if(options.show.ready || options.prerender) { hoverIntent(event); }
1576
+ })
1577
+ .attr('data-hasqtip', TRUE);
1578
+ };
1579
+
1580
+ // Setup base plugins
1581
+ PLUGINS = QTIP.plugins = {
1582
+ // Corner object parser
1583
+ Corner: function(corner) {
1584
+ corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();
1585
+ this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
1586
+ this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
1587
+
1588
+ var f = corner.charAt(0); this.precedance = (f === 't' || f === 'b' ? Y : X);
1589
+
1590
+ this.string = function() { return this.precedance === Y ? this.y+this.x : this.x+this.y; };
1591
+ this.abbrev = function() {
1592
+ var x = this.x.substr(0,1), y = this.y.substr(0,1);
1593
+ return x === y ? x : this.precedance === Y ? y + x : x + y;
1594
+ };
1595
+
1596
+ this.invertx = function(center) { this.x = this.x === LEFT ? RIGHT : this.x === RIGHT ? LEFT : center || this.x; };
1597
+ this.inverty = function(center) { this.y = this.y === TOP ? BOTTOM : this.y === BOTTOM ? TOP : center || this.y; };
1598
+
1599
+ this.clone = function() {
1600
+ return {
1601
+ x: this.x, y: this.y, precedance: this.precedance,
1602
+ string: this.string, abbrev: this.abbrev, clone: this.clone,
1603
+ invertx: this.invertx, inverty: this.inverty
1604
+ };
1605
+ };
1606
+ },
1607
+
1608
+ // Custom (more correct for qTip!) offset calculator
1609
+ offset: function(elem, container) {
1610
+ var pos = elem.offset(),
1611
+ docBody = elem.closest('body'),
1612
+ quirks = $.browser.msie && document.compatMode !== 'CSS1Compat',
1613
+ parent = container, scrolled,
1614
+ coffset, overflow;
1615
+
1616
+ function scroll(e, i) {
1617
+ pos.left += i * e.scrollLeft();
1618
+ pos.top += i * e.scrollTop();
1619
+ }
1620
+
1621
+ if(parent) {
1622
+ // Compensate for non-static containers offset
1623
+ do {
1624
+ if(parent.css('position') !== 'static') {
1625
+ coffset = parent.position();
1626
+
1627
+ // Account for element positioning, borders and margins
1628
+ pos.left -= coffset.left + (parseInt(parent.css('borderLeftWidth'), 10) || 0) + (parseInt(parent.css('marginLeft'), 10) || 0);
1629
+ pos.top -= coffset.top + (parseInt(parent.css('borderTopWidth'), 10) || 0) + (parseInt(parent.css('marginTop'), 10) || 0);
1630
+
1631
+ // If this is the first parent element with an overflow of "scroll" or "auto", store it
1632
+ if(!scrolled && (overflow = parent.css('overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = parent; }
1633
+ }
1634
+ }
1635
+ while((parent = $(parent[0].offsetParent)).length);
1636
+
1637
+ // Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)
1638
+ if(scrolled && scrolled[0] !== docBody[0] || quirks) {
1639
+ scroll( scrolled || docBody, 1 );
1640
+ }
1641
+ }
1642
+
1643
+ return pos;
1644
+ },
1645
+
1646
+ /*
1647
+ * iOS version detection
1648
+ */
1649
+ iOS: parseFloat(
1650
+ ('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
1651
+ .replace('undefined', '3_2').replace('_', '.').replace('_', '')
1652
+ ) || FALSE,
1653
+
1654
+ /*
1655
+ * jQuery-specific $.fn overrides
1656
+ */
1657
+ fn: {
1658
+ /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
1659
+ attr: function(attr, val) {
1660
+ if(this.length) {
1661
+ var self = this[0],
1662
+ title = 'title',
1663
+ api = $.data(self, 'qtip');
1664
+
1665
+ if(attr === title && api && 'object' === typeof api && api.options.suppress) {
1666
+ if(arguments.length < 2) {
1667
+ return $.attr(self, oldtitle);
1668
+ }
1669
+
1670
+ // If qTip is rendered and title was originally used as content, update it
1671
+ if(api && api.options.content.attr === title && api.cache.attr) {
1672
+ api.set('content.text', val);
1673
+ }
1674
+
1675
+ // Use the regular attr method to set, then cache the result
1676
+ return this.attr(oldtitle, val);
1677
+ }
1678
+ }
1679
+
1680
+ return $.fn['attr'+replaceSuffix].apply(this, arguments);
1681
+ },
1682
+
1683
+ /* Allow clone to correctly retrieve cached title attributes */
1684
+ clone: function(keepData) {
1685
+ var titles = $([]), title = 'title',
1686
+
1687
+ // Clone our element using the real clone method
1688
+ elems = $.fn['clone'+replaceSuffix].apply(this, arguments);
1689
+
1690
+ // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
1691
+ if(!keepData) {
1692
+ elems.filter('['+oldtitle+']').attr('title', function() {
1693
+ return $.attr(this, oldtitle);
1694
+ })
1695
+ .removeAttr(oldtitle);
1696
+ }
1697
+
1698
+ return elems;
1699
+ }
1700
+ }
1701
+ };
1702
+
1703
+ // Apply the fn overrides above
1704
+ $.each(PLUGINS.fn, function(name, func) {
1705
+ if(!func || $.fn[name+replaceSuffix]) { return TRUE; }
1706
+
1707
+ var old = $.fn[name+replaceSuffix] = $.fn[name];
1708
+ $.fn[name] = function() {
1709
+ return func.apply(this, arguments) || old.apply(this, arguments);
1710
+ };
1711
+ });
1712
+
1713
+ /* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
1714
+ * This snippet is taken directly from jQuery UI source code found here:
1715
+ * http://code.jquery.com/ui/jquery-ui-git.js
1716
+ */
1717
+ if(!$.ui) {
1718
+ $['cleanData'+replaceSuffix] = $.cleanData;
1719
+ $.cleanData = function( elems ) {
1720
+ for(var i = 0, elem; (elem = elems[i]) !== undefined; i++) {
1721
+ try { $( elem ).triggerHandler('removeqtip'); }
1722
+ catch( e ) {}
1723
+ }
1724
+ $['cleanData'+replaceSuffix]( elems );
1725
+ };
1726
+ }
1727
+
1728
+ // Set global qTip properties
1729
+ QTIP.version = '2.0.0-nightly-15f5c6bc20';
1730
+ QTIP.nextid = 0;
1731
+ QTIP.inactiveEvents = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' ');
1732
+ QTIP.zindex = 15000;
1733
+
1734
+ // Define configuration defaults
1735
+ QTIP.defaults = {
1736
+ prerender: FALSE,
1737
+ id: FALSE,
1738
+ overwrite: TRUE,
1739
+ suppress: TRUE,
1740
+ content: {
1741
+ text: TRUE,
1742
+ attr: 'title',
1743
+ deferred: FALSE,
1744
+ title: {
1745
+ text: FALSE,
1746
+ button: FALSE
1747
+ }
1748
+ },
1749
+ position: {
1750
+ my: 'top left',
1751
+ at: 'bottom right',
1752
+ target: FALSE,
1753
+ container: FALSE,
1754
+ viewport: FALSE,
1755
+ adjust: {
1756
+ x: 0, y: 0,
1757
+ mouse: TRUE,
1758
+ resize: TRUE,
1759
+ method: 'flipinvert flipinvert'
1760
+ },
1761
+ effect: function(api, pos, viewport) {
1762
+ $(this).animate(pos, {
1763
+ duration: 200,
1764
+ queue: FALSE
1765
+ });
1766
+ }
1767
+ },
1768
+ show: {
1769
+ target: FALSE,
1770
+ event: 'mouseenter',
1771
+ effect: TRUE,
1772
+ delay: 90,
1773
+ solo: FALSE,
1774
+ ready: FALSE,
1775
+ autofocus: FALSE
1776
+ },
1777
+ hide: {
1778
+ target: FALSE,
1779
+ event: 'mouseleave',
1780
+ effect: TRUE,
1781
+ delay: 0,
1782
+ fixed: FALSE,
1783
+ inactive: FALSE,
1784
+ leave: 'window',
1785
+ distance: FALSE
1786
+ },
1787
+ style: {
1788
+ classes: '',
1789
+ widget: FALSE,
1790
+ width: FALSE,
1791
+ height: FALSE,
1792
+ def: TRUE
1793
+ },
1794
+ events: {
1795
+ render: NULL,
1796
+ move: NULL,
1797
+ show: NULL,
1798
+ hide: NULL,
1799
+ toggle: NULL,
1800
+ visible: NULL,
1801
+ hidden: NULL,
1802
+ focus: NULL,
1803
+ blur: NULL
1804
+ }
1805
+ };
1806
+
1807
+
1808
+ PLUGINS.svg = function(api, svg, corner, adjustMethod)
1809
+ {
1810
+ var doc = $(document),
1811
+ elem = svg[0],
1812
+ result = {
1813
+ width: 0, height: 0,
1814
+ position: { top: 1e10, left: 1e10 }
1815
+ },
1816
+ box, mtx, root, point, tPoint;
1817
+
1818
+ // Ascend the parentNode chain until we find an element with getBBox()
1819
+ while(!elem.getBBox) { elem = elem.parentNode; }
1820
+
1821
+ // Check for a valid bounding box method
1822
+ if (elem.getBBox && elem.parentNode) {
1823
+ box = elem.getBBox();
1824
+ mtx = elem.getScreenCTM();
1825
+ root = elem.farthestViewportElement || elem;
1826
+
1827
+ // Return if no method is found
1828
+ if(!root.createSVGPoint) { return result; }
1829
+
1830
+ // Create our point var
1831
+ point = root.createSVGPoint();
1832
+
1833
+ // Adjust top and left
1834
+ point.x = box.x;
1835
+ point.y = box.y;
1836
+ tPoint = point.matrixTransform(mtx);
1837
+ result.position.left = tPoint.x;
1838
+ result.position.top = tPoint.y;
1839
+
1840
+ // Adjust width and height
1841
+ point.x += box.width;
1842
+ point.y += box.height;
1843
+ tPoint = point.matrixTransform(mtx);
1844
+ result.width = tPoint.x - result.position.left;
1845
+ result.height = tPoint.y - result.position.top;
1846
+
1847
+ // Adjust by scroll offset
1848
+ result.position.left += doc.scrollLeft();
1849
+ result.position.top += doc.scrollTop();
1850
+ }
1851
+
1852
+ return result;
1853
+ };
1854
+
1855
+
1856
+ function Ajax(api)
1857
+ {
1858
+ var self = this,
1859
+ tooltip = api.elements.tooltip,
1860
+ opts = api.options.content.ajax,
1861
+ defaults = QTIP.defaults.content.ajax,
1862
+ namespace = '.qtip-ajax',
1863
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
1864
+ first = TRUE,
1865
+ stop = FALSE,
1866
+ xhr;
1867
+
1868
+ api.checks.ajax = {
1869
+ '^content.ajax': function(obj, name, v) {
1870
+ // If content.ajax object was reset, set our local var
1871
+ if(name === 'ajax') { opts = v; }
1872
+
1873
+ if(name === 'once') {
1874
+ self.init();
1875
+ }
1876
+ else if(opts && opts.url) {
1877
+ self.load();
1878
+ }
1879
+ else {
1880
+ tooltip.unbind(namespace);
1881
+ }
1882
+ }
1883
+ };
1884
+
1885
+ $.extend(self, {
1886
+ init: function() {
1887
+ // Make sure ajax options are enabled and bind event
1888
+ if(opts && opts.url) {
1889
+ tooltip.unbind(namespace)[ opts.once ? 'one' : 'bind' ]('tooltipshow'+namespace, self.load);
1890
+ }
1891
+
1892
+ return self;
1893
+ },
1894
+
1895
+ load: function(event) {
1896
+ if(stop) {stop = FALSE; return; }
1897
+
1898
+ var hasSelector = opts.url.lastIndexOf(' '),
1899
+ url = opts.url,
1900
+ selector,
1901
+ hideFirst = !opts.loading && first;
1902
+
1903
+ // If loading option is disabled, prevent the tooltip showing until we've completed the request
1904
+ if(hideFirst) { try{ event.preventDefault(); } catch(e) {} }
1905
+
1906
+ // Make sure default event hasn't been prevented
1907
+ else if(event && event.isDefaultPrevented()) { return self; }
1908
+
1909
+ // Cancel old request
1910
+ if(xhr && xhr.abort) { xhr.abort(); }
1911
+
1912
+ // Check if user delcared a content selector like in .load()
1913
+ if(hasSelector > -1) {
1914
+ selector = url.substr(hasSelector);
1915
+ url = url.substr(0, hasSelector);
1916
+ }
1917
+
1918
+ // Define common after callback for both success/error handlers
1919
+ function after() {
1920
+ var complete;
1921
+
1922
+ // Don't proceed if tooltip is destroyed
1923
+ if(api.destroyed) { return; }
1924
+
1925
+ // Set first flag to false
1926
+ first = FALSE;
1927
+
1928
+ // Re-display tip if loading and first time, and reset first flag
1929
+ if(hideFirst) { stop = TRUE; api.show(event.originalEvent); }
1930
+
1931
+ // Call users complete method if it was defined
1932
+ if((complete = defaults.complete || opts.complete) && $.isFunction(complete)) {
1933
+ complete.apply(opts.context || api, arguments);
1934
+ }
1935
+ }
1936
+
1937
+ // Define success handler
1938
+ function successHandler(content, status, jqXHR) {
1939
+ var success;
1940
+
1941
+ // Don't proceed if tooltip is destroyed
1942
+ if(api.destroyed) { return; }
1943
+
1944
+ // If URL contains a selector
1945
+ if(selector && 'string' === typeof content) {
1946
+ // Create a dummy div to hold the results and grab the selector element
1947
+ content = $('<div/>')
1948
+ // inject the contents of the document in, removing the scripts
1949
+ // to avoid any 'Permission Denied' errors in IE
1950
+ .append(content.replace(rscript, ""))
1951
+
1952
+ // Locate the specified elements
1953
+ .find(selector);
1954
+ }
1955
+
1956
+ // Call the success function if one is defined
1957
+ if((success = defaults.success || opts.success) && $.isFunction(success)) {
1958
+ success.call(opts.context || api, content, status, jqXHR);
1959
+ }
1960
+
1961
+ // Otherwise set the content
1962
+ else { api.set('content.text', content); }
1963
+ }
1964
+
1965
+ // Error handler
1966
+ function errorHandler(xhr, status, error) {
1967
+ if(api.destroyed || xhr.status === 0) { return; }
1968
+ api.set('content.text', status + ': ' + error);
1969
+ }
1970
+
1971
+ // Setup $.ajax option object and process the request
1972
+ xhr = $.ajax(
1973
+ $.extend({
1974
+ error: defaults.error || errorHandler,
1975
+ context: api
1976
+ },
1977
+ opts, { url: url, success: successHandler, complete: after })
1978
+ );
1979
+ },
1980
+
1981
+ destroy: function() {
1982
+ // Cancel ajax request if possible
1983
+ if(xhr && xhr.abort) { xhr.abort(); }
1984
+
1985
+ // Set api.destroyed flag
1986
+ api.destroyed = TRUE;
1987
+ }
1988
+ });
1989
+
1990
+ self.init();
1991
+ }
1992
+
1993
+
1994
+ PLUGINS.ajax = function(api)
1995
+ {
1996
+ var self = api.plugins.ajax;
1997
+
1998
+ return 'object' === typeof self ? self : (api.plugins.ajax = new Ajax(api));
1999
+ };
2000
+
2001
+ PLUGINS.ajax.initialize = 'render';
2002
+
2003
+ // Setup plugin sanitization
2004
+ PLUGINS.ajax.sanitize = function(options)
2005
+ {
2006
+ var content = options.content, opts;
2007
+ if(content && 'ajax' in content) {
2008
+ opts = content.ajax;
2009
+ if(typeof opts !== 'object') { opts = options.content.ajax = { url: opts }; }
2010
+ if('boolean' !== typeof opts.once && opts.once) { opts.once = !!opts.once; }
2011
+ }
2012
+ };
2013
+
2014
+ // Extend original api defaults
2015
+ $.extend(TRUE, QTIP.defaults, {
2016
+ content: {
2017
+ ajax: {
2018
+ loading: TRUE,
2019
+ once: TRUE
2020
+ }
2021
+ }
2022
+ });
2023
+
2024
+
2025
+ // Tip coordinates calculator
2026
+ function calculateTip(corner, width, height)
2027
+ {
2028
+ var width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),
2029
+
2030
+ // Define tip coordinates in terms of height and width values
2031
+ tips = {
2032
+ bottomright: [[0,0], [width,height], [width,0]],
2033
+ bottomleft: [[0,0], [width,0], [0,height]],
2034
+ topright: [[0,height], [width,0], [width,height]],
2035
+ topleft: [[0,0], [0,height], [width,height]],
2036
+ topcenter: [[0,height], [width2,0], [width,height]],
2037
+ bottomcenter: [[0,0], [width,0], [width2,height]],
2038
+ rightcenter: [[0,0], [width,height2], [0,height]],
2039
+ leftcenter: [[width,0], [width,height], [0,height2]]
2040
+ };
2041
+
2042
+ // Set common side shapes
2043
+ tips.lefttop = tips.bottomright; tips.righttop = tips.bottomleft;
2044
+ tips.leftbottom = tips.topright; tips.rightbottom = tips.topleft;
2045
+
2046
+ return tips[ corner.string() ];
2047
+ }
2048
+
2049
+
2050
+ function Tip(qTip, command)
2051
+ {
2052
+ var self = this,
2053
+ opts = qTip.options.style.tip,
2054
+ elems = qTip.elements,
2055
+ tooltip = elems.tooltip,
2056
+ cache = { top: 0, left: 0 },
2057
+ size = {
2058
+ width: opts.width,
2059
+ height: opts.height
2060
+ },
2061
+ color = { },
2062
+ border = opts.border || 0,
2063
+ namespace = '.qtip-tip',
2064
+ hasCanvas = !!($('<canvas />')[0] || {}).getContext,
2065
+ tiphtml;
2066
+
2067
+ self.corner = NULL;
2068
+ self.mimic = NULL;
2069
+ self.border = border;
2070
+ self.offset = opts.offset;
2071
+ self.size = size;
2072
+
2073
+ // Add new option checks for the plugin
2074
+ qTip.checks.tip = {
2075
+ '^position.my|style.tip.(corner|mimic|border)$': function() {
2076
+ // Make sure a tip can be drawn
2077
+ if(!self.init()) {
2078
+ self.destroy();
2079
+ }
2080
+
2081
+ // Reposition the tooltip
2082
+ qTip.reposition();
2083
+ },
2084
+ '^style.tip.(height|width)$': function() {
2085
+ // Re-set dimensions and redraw the tip
2086
+ size = {
2087
+ width: opts.width,
2088
+ height: opts.height
2089
+ };
2090
+ self.create();
2091
+ self.update();
2092
+
2093
+ // Reposition the tooltip
2094
+ qTip.reposition();
2095
+ },
2096
+ '^content.title.text|style.(classes|widget)$': function() {
2097
+ if(elems.tip && elems.tip.length) {
2098
+ self.update();
2099
+ }
2100
+ }
2101
+ };
2102
+
2103
+ function whileVisible(callback) {
2104
+ var visible = tooltip.is(':visible');
2105
+ tooltip.show(); callback(); tooltip.toggle(visible);
2106
+ }
2107
+
2108
+ function swapDimensions() {
2109
+ size.width = opts.height;
2110
+ size.height = opts.width;
2111
+ }
2112
+
2113
+ function resetDimensions() {
2114
+ size.width = opts.width;
2115
+ size.height = opts.height;
2116
+ }
2117
+
2118
+ function reposition(event, api, pos, viewport) {
2119
+ if(!elems.tip) { return; }
2120
+
2121
+ var newCorner = self.corner.clone(),
2122
+ adjust = pos.adjusted,
2123
+ method = qTip.options.position.adjust.method.split(' '),
2124
+ horizontal = method[0],
2125
+ vertical = method[1] || method[0],
2126
+ shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
2127
+ offset, css = {}, props;
2128
+
2129
+ // If our tip position isn't fixed e.g. doesn't adjust with viewport...
2130
+ if(self.corner.fixed !== TRUE) {
2131
+ // Horizontal - Shift or flip method
2132
+ if(horizontal === SHIFT && newCorner.precedance === X && adjust.left && newCorner.y !== CENTER) {
2133
+ newCorner.precedance = newCorner.precedance === X ? Y : X;
2134
+ }
2135
+ else if(horizontal !== SHIFT && adjust.left){
2136
+ newCorner.x = newCorner.x === CENTER ? (adjust.left > 0 ? LEFT : RIGHT) : (newCorner.x === LEFT ? RIGHT : LEFT);
2137
+ }
2138
+
2139
+ // Vertical - Shift or flip method
2140
+ if(vertical === SHIFT && newCorner.precedance === Y && adjust.top && newCorner.x !== CENTER) {
2141
+ newCorner.precedance = newCorner.precedance === Y ? X : Y;
2142
+ }
2143
+ else if(vertical !== SHIFT && adjust.top) {
2144
+ newCorner.y = newCorner.y === CENTER ? (adjust.top > 0 ? TOP : BOTTOM) : (newCorner.y === TOP ? BOTTOM : TOP);
2145
+ }
2146
+
2147
+ // Update and redraw the tip if needed (check cached details of last drawn tip)
2148
+ if(newCorner.string() !== cache.corner.string() && (cache.top !== adjust.top || cache.left !== adjust.left)) {
2149
+ self.update(newCorner, FALSE);
2150
+ }
2151
+ }
2152
+
2153
+ // Setup tip offset properties
2154
+ offset = self.position(newCorner, adjust);
2155
+ offset[ newCorner.x ] += parseWidth(newCorner, newCorner.x);
2156
+ offset[ newCorner.y ] += parseWidth(newCorner, newCorner.y);
2157
+
2158
+ // Readjust offset object to make it left/top
2159
+ if(offset.right !== undefined) { offset.left = -offset.right; }
2160
+ if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
2161
+ offset.user = Math.max(0, opts.offset);
2162
+
2163
+ // Viewport "shift" specific adjustments
2164
+ if(shift.left = (horizontal === SHIFT && !!adjust.left)) {
2165
+ if(newCorner.x === CENTER) {
2166
+ css['margin-left'] = shift.x = offset['margin-left'];
2167
+ }
2168
+ else {
2169
+ props = offset.right !== undefined ?
2170
+ [ adjust.left, -offset.left ] : [ -adjust.left, offset.left ];
2171
+
2172
+ if( (shift.x = Math.max(props[0], props[1])) > props[0] ) {
2173
+ pos.left -= adjust.left;
2174
+ shift.left = FALSE;
2175
+ }
2176
+
2177
+ css[ offset.right !== undefined ? RIGHT : LEFT ] = shift.x;
2178
+ }
2179
+ }
2180
+ if(shift.top = (vertical === SHIFT && !!adjust.top)) {
2181
+ if(newCorner.y === CENTER) {
2182
+ css['margin-top'] = shift.y = offset['margin-top'];
2183
+ }
2184
+ else {
2185
+ props = offset.bottom !== undefined ?
2186
+ [ adjust.top, -offset.top ] : [ -adjust.top, offset.top ];
2187
+
2188
+ if( (shift.y = Math.max(props[0], props[1])) > props[0] ) {
2189
+ pos.top -= adjust.top;
2190
+ shift.top = FALSE;
2191
+ }
2192
+
2193
+ css[ offset.bottom !== undefined ? BOTTOM : TOP ] = shift.y;
2194
+ }
2195
+ }
2196
+
2197
+ /*
2198
+ * If the tip is adjusted in both dimensions, or in a
2199
+ * direction that would cause it to be anywhere but the
2200
+ * outer border, hide it!
2201
+ */
2202
+ elems.tip.css(css).toggle(
2203
+ !((shift.x && shift.y) || (newCorner.x === CENTER && shift.y) || (newCorner.y === CENTER && shift.x))
2204
+ );
2205
+
2206
+ // Adjust position to accomodate tip dimensions
2207
+ pos.left -= offset.left.charAt ? offset.user : horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left : 0;
2208
+ pos.top -= offset.top.charAt ? offset.user : vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top : 0;
2209
+
2210
+ // Cache details
2211
+ cache.left = adjust.left; cache.top = adjust.top;
2212
+ cache.corner = newCorner.clone();
2213
+ }
2214
+
2215
+ function parseCorner() {
2216
+ var corner = opts.corner,
2217
+ posOptions = qTip.options.position,
2218
+ at = posOptions.at,
2219
+ my = posOptions.my.string ? posOptions.my.string() : posOptions.my;
2220
+
2221
+ // Detect corner and mimic properties
2222
+ if(corner === FALSE || (my === FALSE && at === FALSE)) {
2223
+ return FALSE;
2224
+ }
2225
+ else {
2226
+ if(corner === TRUE) {
2227
+ self.corner = new PLUGINS.Corner(my);
2228
+ }
2229
+ else if(!corner.string) {
2230
+ self.corner = new PLUGINS.Corner(corner);
2231
+ self.corner.fixed = TRUE;
2232
+ }
2233
+ }
2234
+
2235
+ // Cache it
2236
+ cache.corner = new PLUGINS.Corner( self.corner.string() );
2237
+
2238
+ return self.corner.string() !== 'centercenter';
2239
+ }
2240
+
2241
+ /* border width calculator */
2242
+ function parseWidth(corner, side, use) {
2243
+ side = !side ? corner[corner.precedance] : side;
2244
+
2245
+ var isTitleTop = elems.titlebar && corner.y === TOP,
2246
+ elem = isTitleTop ? elems.titlebar : tooltip,
2247
+ borderSide = 'border-' + side + '-width',
2248
+ css = function(elem) { return parseInt(elem.css(borderSide), 10); },
2249
+ val;
2250
+
2251
+ // Grab the border-width value (make tooltip visible first)
2252
+ whileVisible(function() {
2253
+ val = (use ? css(use) : (css(elems.content) || css(elem) || css(tooltip))) || 0;
2254
+ });
2255
+ return val;
2256
+ }
2257
+
2258
+ function parseRadius(corner) {
2259
+ var isTitleTop = elems.titlebar && corner.y === TOP,
2260
+ elem = isTitleTop ? elems.titlebar : elems.content,
2261
+ moz = $.browser.mozilla,
2262
+ prefix = moz ? '-moz-' : $.browser.webkit ? '-webkit-' : '',
2263
+ nonStandard = 'border-radius-' + corner.y + corner.x,
2264
+ standard = 'border-' + corner.y + '-' + corner.x + '-radius',
2265
+ css = function(c) { return parseInt(elem.css(c), 10) || parseInt(tooltip.css(c), 10); },
2266
+ val;
2267
+
2268
+ whileVisible(function() {
2269
+ val = css(standard) || css(prefix + standard) || css(prefix + nonStandard) || css(nonStandard) || 0;
2270
+ });
2271
+ return val;
2272
+ }
2273
+
2274
+ function parseColours(actual) {
2275
+ var i, fill, border,
2276
+ tip = elems.tip.css('cssText', ''),
2277
+ corner = actual || self.corner,
2278
+ invalid = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i,
2279
+ borderSide = 'border-' + corner[ corner.precedance ] + '-color',
2280
+ bgColor = 'background-color',
2281
+ transparent = 'transparent',
2282
+ important = ' !important',
2283
+
2284
+ titlebar = elems.titlebar,
2285
+ useTitle = titlebar && (corner.y === TOP || (corner.y === CENTER && tip.position().top + (size.height / 2) + opts.offset < titlebar.outerHeight(TRUE))),
2286
+ colorElem = useTitle ? titlebar : elems.content;
2287
+
2288
+ function css(elem, prop, compare) {
2289
+ var val = elem.css(prop) || transparent;
2290
+ if(compare && val === elem.css(compare)) { return FALSE; }
2291
+ else { return invalid.test(val) ? FALSE : val; }
2292
+ }
2293
+
2294
+ // Ensure tooltip is visible then...
2295
+ whileVisible(function() {
2296
+ // Attempt to detect the background colour from various elements, left-to-right precedance
2297
+ color.fill = css(tip, bgColor) || css(colorElem, bgColor) || css(elems.content, bgColor) ||
2298
+ css(tooltip, bgColor) || tip.css(bgColor);
2299
+
2300
+ // Attempt to detect the correct border side colour from various elements, left-to-right precedance
2301
+ color.border = css(tip, borderSide, 'color') || css(colorElem, borderSide, 'color') ||
2302
+ css(elems.content, borderSide, 'color') || css(tooltip, borderSide, 'color') || tooltip.css(borderSide);
2303
+
2304
+ // Reset background and border colours
2305
+ $('*', tip).add(tip).css('cssText', bgColor+':'+transparent+important+';border:0'+important+';');
2306
+ });
2307
+ }
2308
+
2309
+ function calculateSize(corner) {
2310
+ var y = corner.precedance === Y,
2311
+ width = size [ y ? WIDTH : HEIGHT ],
2312
+ height = size [ y ? HEIGHT : WIDTH ],
2313
+ isCenter = corner.string().indexOf(CENTER) > -1,
2314
+ base = width * (isCenter ? 0.5 : 1),
2315
+ pow = Math.pow,
2316
+ round = Math.round,
2317
+ bigHyp, ratio, result,
2318
+
2319
+ smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),
2320
+
2321
+ hyp = [
2322
+ (border / base) * smallHyp, (border / height) * smallHyp
2323
+ ];
2324
+ hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(border, 2) );
2325
+ hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(border, 2) );
2326
+
2327
+ bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
2328
+ ratio = bigHyp / smallHyp;
2329
+
2330
+ result = [ round(ratio * height), round(ratio * width) ];
2331
+ return { height: result[ y ? 0 : 1 ], width: result[ y ? 1 : 0 ] };
2332
+ }
2333
+
2334
+ function createVML(tag, props, style) {
2335
+ return '<qvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+
2336
+ ' style="behavior: url(#default#VML); '+(style||'')+ '" />';
2337
+ }
2338
+
2339
+ $.extend(self, {
2340
+ init: function()
2341
+ {
2342
+ var enabled = parseCorner() && (hasCanvas || $.browser.msie);
2343
+
2344
+ // Determine tip corner and type
2345
+ if(enabled) {
2346
+ // Create a new tip and draw it
2347
+ self.create();
2348
+ self.update();
2349
+
2350
+ // Bind update events
2351
+ tooltip.unbind(namespace).bind('tooltipmove'+namespace, reposition);
2352
+ }
2353
+
2354
+ return enabled;
2355
+ },
2356
+
2357
+ create: function()
2358
+ {
2359
+ var width = size.width,
2360
+ height = size.height,
2361
+ vml;
2362
+
2363
+ // Remove previous tip element if present
2364
+ if(elems.tip) { elems.tip.remove(); }
2365
+
2366
+ // Create tip element and prepend to the tooltip
2367
+ elems.tip = $('<div />', { 'class': 'qtip-tip' }).css({ width: width, height: height }).prependTo(tooltip);
2368
+
2369
+ // Create tip drawing element(s)
2370
+ if(hasCanvas) {
2371
+ // save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
2372
+ $('<canvas />').appendTo(elems.tip)[0].getContext('2d').save();
2373
+ }
2374
+ else {
2375
+ vml = createVML('shape', 'coordorigin="0,0"', 'position:absolute;');
2376
+ elems.tip.html(vml + vml);
2377
+
2378
+ // Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML
2379
+ $('*', elems.tip).bind('click mousedown', function(event) { event.stopPropagation(); });
2380
+ }
2381
+ },
2382
+
2383
+ update: function(corner, position)
2384
+ {
2385
+ var tip = elems.tip,
2386
+ inner = tip.children(),
2387
+ width = size.width,
2388
+ height = size.height,
2389
+ mimic = opts.mimic,
2390
+ round = Math.round,
2391
+ precedance, context, coords, translate, newSize;
2392
+
2393
+ // Re-determine tip if not already set
2394
+ if(!corner) { corner = cache.corner || self.corner; }
2395
+
2396
+ // Use corner property if we detect an invalid mimic value
2397
+ if(mimic === FALSE) { mimic = corner; }
2398
+
2399
+ // Otherwise inherit mimic properties from the corner object as necessary
2400
+ else {
2401
+ mimic = new PLUGINS.Corner(mimic);
2402
+ mimic.precedance = corner.precedance;
2403
+
2404
+ if(mimic.x === 'inherit') { mimic.x = corner.x; }
2405
+ else if(mimic.y === 'inherit') { mimic.y = corner.y; }
2406
+ else if(mimic.x === mimic.y) {
2407
+ mimic[ corner.precedance ] = corner[ corner.precedance ];
2408
+ }
2409
+ }
2410
+ precedance = mimic.precedance;
2411
+
2412
+ // Ensure the tip width.height are relative to the tip position
2413
+ if(corner.precedance === X) { swapDimensions(); }
2414
+ else { resetDimensions(); }
2415
+
2416
+ // Set the tip dimensions
2417
+ elems.tip.css({
2418
+ width: (width = size.width),
2419
+ height: (height = size.height)
2420
+ });
2421
+
2422
+ // Update our colours
2423
+ parseColours(corner);
2424
+
2425
+ // Detect border width, taking into account colours
2426
+ if(color.border !== 'transparent') {
2427
+ // Grab border width
2428
+ border = parseWidth(corner, NULL);
2429
+
2430
+ // If border width isn't zero, use border color as fill (1.0 style tips)
2431
+ if(opts.border === 0 && border > 0) { color.fill = color.border; }
2432
+
2433
+ // Set border width (use detected border width if opts.border is true)
2434
+ self.border = border = opts.border !== TRUE ? opts.border : border;
2435
+ }
2436
+
2437
+ // Border colour was invalid, set border to zero
2438
+ else { self.border = border = 0; }
2439
+
2440
+ // Calculate coordinates
2441
+ coords = calculateTip(mimic, width , height);
2442
+
2443
+ // Determine tip size
2444
+ self.size = newSize = calculateSize(corner);
2445
+ tip.css(newSize).css('line-height', newSize.height+'px');
2446
+
2447
+ // Calculate tip translation
2448
+ if(corner.precedance === Y) {
2449
+ translate = [
2450
+ round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize.width - width - border : (newSize.width - width) / 2),
2451
+ round(mimic.y === TOP ? newSize.height - height : 0)
2452
+ ];
2453
+ }
2454
+ else {
2455
+ translate = [
2456
+ round(mimic.x === LEFT ? newSize.width - width : 0),
2457
+ round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize.height - height - border : (newSize.height - height) / 2)
2458
+ ];
2459
+ }
2460
+
2461
+ // Canvas drawing implementation
2462
+ if(hasCanvas) {
2463
+ // Set the canvas size using calculated size
2464
+ inner.attr(newSize);
2465
+
2466
+ // Grab canvas context and clear/save it
2467
+ context = inner[0].getContext('2d');
2468
+ context.restore(); context.save();
2469
+ context.clearRect(0,0,3000,3000);
2470
+
2471
+ // Set properties
2472
+ context.fillStyle = color.fill;
2473
+ context.strokeStyle = color.border;
2474
+ context.lineWidth = border * 2;
2475
+ context.lineJoin = 'miter';
2476
+ context.miterLimit = 100;
2477
+
2478
+ // Translate origin
2479
+ context.translate(translate[0], translate[1]);
2480
+
2481
+ // Draw the tip
2482
+ context.beginPath();
2483
+ context.moveTo(coords[0][0], coords[0][1]);
2484
+ context.lineTo(coords[1][0], coords[1][1]);
2485
+ context.lineTo(coords[2][0], coords[2][1]);
2486
+ context.closePath();
2487
+
2488
+ // Apply fill and border
2489
+ if(border) {
2490
+ // Make sure transparent borders are supported by doing a stroke
2491
+ // of the background colour before the stroke colour
2492
+ if(tooltip.css('background-clip') === 'border-box') {
2493
+ context.strokeStyle = color.fill;
2494
+ context.stroke();
2495
+ }
2496
+ context.strokeStyle = color.border;
2497
+ context.stroke();
2498
+ }
2499
+ context.fill();
2500
+ }
2501
+
2502
+ // VML (IE Proprietary implementation)
2503
+ else {
2504
+ // Setup coordinates string
2505
+ coords = 'm' + coords[0][0] + ',' + coords[0][1] + ' l' + coords[1][0] +
2506
+ ',' + coords[1][1] + ' ' + coords[2][0] + ',' + coords[2][1] + ' xe';
2507
+
2508
+ // Setup VML-specific offset for pixel-perfection
2509
+ translate[2] = border && /^(r|b)/i.test(corner.string()) ?
2510
+ parseFloat($.browser.version, 10) === 8 ? 2 : 1 : 0;
2511
+
2512
+ // Set initial CSS
2513
+ inner.css({
2514
+ coordsize: (width+border) + ' ' + (height+border),
2515
+ antialias: ''+(mimic.string().indexOf(CENTER) > -1),
2516
+ left: translate[0],
2517
+ top: translate[1],
2518
+ width: width + border,
2519
+ height: height + border
2520
+ })
2521
+ .each(function(i) {
2522
+ var $this = $(this);
2523
+
2524
+ // Set shape specific attributes
2525
+ $this[ $this.prop ? 'prop' : 'attr' ]({
2526
+ coordsize: (width+border) + ' ' + (height+border),
2527
+ path: coords,
2528
+ fillcolor: color.fill,
2529
+ filled: !!i,
2530
+ stroked: !i
2531
+ })
2532
+ .toggle(!!(border || i));
2533
+
2534
+ // Check if border is enabled and add stroke element
2535
+ if(!i && $this.html() === '') {
2536
+ $this.html(
2537
+ createVML('stroke', 'weight="'+(border*2)+'px" color="'+color.border+'" miterlimit="1000" joinstyle="miter"')
2538
+ );
2539
+ }
2540
+ });
2541
+ }
2542
+
2543
+ // Position if needed
2544
+ if(position !== FALSE) { self.position(corner); }
2545
+ },
2546
+
2547
+ // Tip positioning method
2548
+ position: function(corner)
2549
+ {
2550
+ var tip = elems.tip,
2551
+ position = {},
2552
+ userOffset = Math.max(0, opts.offset),
2553
+ precedance, dimensions, corners;
2554
+
2555
+ // Return if tips are disabled or tip is not yet rendered
2556
+ if(opts.corner === FALSE || !tip) { return FALSE; }
2557
+
2558
+ // Inherit corner if not provided
2559
+ corner = corner || self.corner;
2560
+ precedance = corner.precedance;
2561
+
2562
+ // Determine which tip dimension to use for adjustment
2563
+ dimensions = calculateSize(corner);
2564
+
2565
+ // Setup corners and offset array
2566
+ corners = [ corner.x, corner.y ];
2567
+ if(precedance === X) { corners.reverse(); }
2568
+
2569
+ // Calculate tip position
2570
+ $.each(corners, function(i, side) {
2571
+ var b, bc, br;
2572
+
2573
+ if(side === CENTER) {
2574
+ b = precedance === Y ? LEFT : TOP;
2575
+ position[ b ] = '50%';
2576
+ position['margin-' + b] = -Math.round(dimensions[ precedance === Y ? WIDTH : HEIGHT ] / 2) + userOffset;
2577
+ }
2578
+ else {
2579
+ b = parseWidth(corner, side);
2580
+ bc = parseWidth(corner, side, elems.content);
2581
+ br = parseRadius(corner);
2582
+
2583
+ position[ side ] = i ? bc : (userOffset + (br > b ? br : -b));
2584
+ }
2585
+ });
2586
+
2587
+ // Adjust for tip dimensions
2588
+ position[ corner[precedance] ] -= dimensions[ precedance === X ? WIDTH : HEIGHT ];
2589
+
2590
+ // Set and return new position
2591
+ tip.css({ top: '', bottom: '', left: '', right: '', margin: '' }).css(position);
2592
+ return position;
2593
+ },
2594
+
2595
+ destroy: function()
2596
+ {
2597
+ // Remove the tip element
2598
+ if(elems.tip) { elems.tip.remove(); }
2599
+ elems.tip = false;
2600
+
2601
+ // Unbind events
2602
+ tooltip.unbind(namespace);
2603
+ }
2604
+ });
2605
+
2606
+ self.init();
2607
+ }
2608
+
2609
+ PLUGINS.tip = function(api)
2610
+ {
2611
+ var self = api.plugins.tip;
2612
+
2613
+ return 'object' === typeof self ? self : (api.plugins.tip = new Tip(api));
2614
+ };
2615
+
2616
+ // Initialize tip on render
2617
+ PLUGINS.tip.initialize = 'render';
2618
+
2619
+ // Setup plugin sanitization options
2620
+ PLUGINS.tip.sanitize = function(options)
2621
+ {
2622
+ var style = options.style, opts;
2623
+ if(style && 'tip' in style) {
2624
+ opts = options.style.tip;
2625
+ if(typeof opts !== 'object'){ options.style.tip = { corner: opts }; }
2626
+ if(!(/string|boolean/i).test(typeof opts['corner'])) { opts['corner'] = TRUE; }
2627
+ if(typeof opts.width !== 'number'){ delete opts.width; }
2628
+ if(typeof opts.height !== 'number'){ delete opts.height; }
2629
+ if(typeof opts.border !== 'number' && opts.border !== TRUE){ delete opts.border; }
2630
+ if(typeof opts.offset !== 'number'){ delete opts.offset; }
2631
+ }
2632
+ };
2633
+
2634
+ // Extend original qTip defaults
2635
+ $.extend(TRUE, QTIP.defaults, {
2636
+ style: {
2637
+ tip: {
2638
+ corner: TRUE,
2639
+ mimic: FALSE,
2640
+ width: 6,
2641
+ height: 6,
2642
+ border: TRUE,
2643
+ offset: 0
2644
+ }
2645
+ }
2646
+ });
2647
+
2648
+
2649
+ function Modal(api)
2650
+ {
2651
+ var self = this,
2652
+ options = api.options.show.modal,
2653
+ elems = api.elements,
2654
+ tooltip = elems.tooltip,
2655
+ overlaySelector = '#qtip-overlay',
2656
+ globalNamespace = '.qtipmodal',
2657
+ namespace = globalNamespace + api.id,
2658
+ attr = 'is-modal-qtip',
2659
+ docBody = $(document.body),
2660
+ focusableSelector = PLUGINS.modal.focusable.join(','),
2661
+ focusableElems = {}, overlay;
2662
+
2663
+ // Setup option set checks
2664
+ api.checks.modal = {
2665
+ '^show.modal.(on|blur)$': function() {
2666
+ // Initialise
2667
+ self.init();
2668
+
2669
+ // Show the modal if not visible already and tooltip is visible
2670
+ elems.overlay.toggle( tooltip.is(':visible') );
2671
+ },
2672
+ '^content.text$': function() {
2673
+ updateFocusable();
2674
+ }
2675
+ };
2676
+
2677
+ function updateFocusable() {
2678
+ focusableElems = $(focusableSelector, tooltip).not('[disabled]').map(function() {
2679
+ return typeof this.focus === 'function' ? this : null;
2680
+ });
2681
+ }
2682
+
2683
+ function focusInputs(blurElems) {
2684
+ // Blurring body element in IE causes window.open windows to unfocus!
2685
+ if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); }
2686
+
2687
+ // Focus the inputs
2688
+ else { focusableElems.first().focus(); }
2689
+ }
2690
+
2691
+ function stealFocus(event) {
2692
+ var target = $(event.target),
2693
+ container = target.closest('.qtip'),
2694
+ targetOnTop;
2695
+
2696
+ // Determine if input container target is above this
2697
+ targetOnTop = container.length < 1 ? FALSE :
2698
+ (parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10));
2699
+
2700
+ // If we're showing a modal, but focus has landed on an input below
2701
+ // this modal, divert focus to the first visible input in this modal
2702
+ // or if we can't find one... the tooltip itself
2703
+ if(!targetOnTop && ($(event.target).closest(selector)[0] !== tooltip[0])) {
2704
+ focusInputs(target);
2705
+ }
2706
+ }
2707
+
2708
+ $.extend(self, {
2709
+ init: function()
2710
+ {
2711
+ // If modal is disabled... return
2712
+ if(!options.on) { return self; }
2713
+
2714
+ // Create the overlay if needed
2715
+ overlay = self.create();
2716
+
2717
+ // Add unique attribute so we can grab modal tooltips easily via a selector
2718
+ tooltip.attr(attr, TRUE)
2719
+
2720
+ // Set z-index
2721
+ .css('z-index', PLUGINS.modal.zindex + $(selector+'['+attr+']').length)
2722
+
2723
+ // Remove previous bound events in globalNamespace
2724
+ .unbind(globalNamespace).unbind(namespace)
2725
+
2726
+ // Apply our show/hide/focus modal events
2727
+ .bind('tooltipshow'+globalNamespace+' tooltiphide'+globalNamespace, function(event, api, duration) {
2728
+ var oEvent = event.originalEvent;
2729
+
2730
+ // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
2731
+ if(event.target === tooltip[0]) {
2732
+ if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(overlay[0]).length) {
2733
+ try { event.preventDefault(); } catch(e) {}
2734
+ }
2735
+ else if(!oEvent || (oEvent && !oEvent.solo)) {
2736
+ self[ event.type.replace('tooltip', '') ](event, duration);
2737
+ }
2738
+ }
2739
+ })
2740
+
2741
+ // Adjust modal z-index on tooltip focus
2742
+ .bind('tooltipfocus'+globalNamespace, function(event) {
2743
+ // If focus was cancelled before it reearch us, don't do anything
2744
+ if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; }
2745
+
2746
+ var qtips = $(selector).filter('['+attr+']'),
2747
+
2748
+ // Keep the modal's lower than other, regular qtips
2749
+ newIndex = PLUGINS.modal.zindex + qtips.length,
2750
+ curIndex = parseInt(tooltip[0].style.zIndex, 10);
2751
+
2752
+ // Set overlay z-index
2753
+ overlay[0].style.zIndex = newIndex - 2;
2754
+
2755
+ // Reduce modal z-index's and keep them properly ordered
2756
+ qtips.each(function() {
2757
+ if(this.style.zIndex > curIndex) {
2758
+ this.style.zIndex -= 1;
2759
+ }
2760
+ });
2761
+
2762
+ // Fire blur event for focused tooltip
2763
+ qtips.end().filter('.' + focusClass).qtip('blur', event.originalEvent);
2764
+
2765
+ // Set the new z-index
2766
+ tooltip.addClass(focusClass)[0].style.zIndex = newIndex;
2767
+
2768
+ // Prevent default handling
2769
+ try { event.preventDefault(); } catch(e) {}
2770
+ })
2771
+
2772
+ // Focus any other visible modals when this one hides
2773
+ .bind('tooltiphide'+globalNamespace, function(event) {
2774
+ if(event.target === tooltip[0]) {
2775
+ $('[' + attr + ']').filter(':visible').not(tooltip).last().qtip('focus', event);
2776
+ }
2777
+ });
2778
+
2779
+ // Apply keyboard "Escape key" close handler
2780
+ if(options.escape) {
2781
+ $(document).unbind(namespace).bind('keydown'+namespace, function(event) {
2782
+ if(event.keyCode === 27 && tooltip.hasClass(focusClass)) {
2783
+ api.hide(event);
2784
+ }
2785
+ });
2786
+ }
2787
+
2788
+ // Apply click handler for blur option
2789
+ if(options.blur) {
2790
+ elems.overlay.unbind(namespace).bind('click'+namespace, function(event) {
2791
+ if(tooltip.hasClass(focusClass)) { api.hide(event); }
2792
+ });
2793
+ }
2794
+
2795
+ // Update focusable elements
2796
+ updateFocusable();
2797
+
2798
+ return self;
2799
+ },
2800
+
2801
+ create: function()
2802
+ {
2803
+ var elem = $(overlaySelector), win = $(window);
2804
+
2805
+ // Return if overlay is already rendered
2806
+ if(elem.length) {
2807
+ // Modal overlay should always be below all tooltips if possible
2808
+ return (elems.overlay = elem.insertAfter( $(selector).last() ));
2809
+ }
2810
+
2811
+ // Create document overlay
2812
+ overlay = elems.overlay = $('<div />', {
2813
+ id: overlaySelector.substr(1),
2814
+ html: '<div></div>',
2815
+ mousedown: function() { return FALSE; }
2816
+ })
2817
+ .hide()
2818
+ .insertAfter( $(selector).last() );
2819
+
2820
+ // Update position on window resize or scroll
2821
+ function resize() {
2822
+ overlay.css({
2823
+ height: win.height(),
2824
+ width: win.width()
2825
+ });
2826
+ }
2827
+ win.unbind(globalNamespace).bind('resize'+globalNamespace, resize);
2828
+ resize(); // Fire it initially too
2829
+
2830
+ return overlay;
2831
+ },
2832
+
2833
+ toggle: function(event, state, duration)
2834
+ {
2835
+ // Make sure default event hasn't been prevented
2836
+ if(event && event.isDefaultPrevented()) { return self; }
2837
+
2838
+ var effect = options.effect,
2839
+ type = state ? 'show': 'hide',
2840
+ visible = overlay.is(':visible'),
2841
+ modals = $('[' + attr + ']').filter(':visible').not(tooltip),
2842
+ zindex;
2843
+
2844
+ // Create our overlay if it isn't present already
2845
+ if(!overlay) { overlay = self.create(); }
2846
+
2847
+ // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
2848
+ if((overlay.is(':animated') && visible === state && overlay.data('toggleState') !== FALSE) || (!state && modals.length)) {
2849
+ return self;
2850
+ }
2851
+
2852
+ // State specific...
2853
+ if(state) {
2854
+ // Set position
2855
+ overlay.css({ left: 0, top: 0 });
2856
+
2857
+ // Toggle backdrop cursor style on show
2858
+ overlay.toggleClass('blurs', options.blur);
2859
+
2860
+ // IF the modal can steal the focus
2861
+ if(options.stealfocus !== FALSE) {
2862
+ // Make sure we can't focus anything outside the tooltip
2863
+ docBody.bind('focusin'+namespace, stealFocus);
2864
+
2865
+ // Blur the current item and focus anything in the modal we an
2866
+ focusInputs( $('body :focus') );
2867
+ }
2868
+ }
2869
+ else {
2870
+ // Undelegate focus handler
2871
+ docBody.unbind('focusin'+namespace);
2872
+ }
2873
+
2874
+ // Stop all animations
2875
+ overlay.stop(TRUE, FALSE).data('toggleState', state);
2876
+
2877
+ // Use custom function if provided
2878
+ if($.isFunction(effect)) {
2879
+ effect.call(overlay, state);
2880
+ }
2881
+
2882
+ // If no effect type is supplied, use a simple toggle
2883
+ else if(effect === FALSE) {
2884
+ overlay[ type ]();
2885
+ }
2886
+
2887
+ // Use basic fade function
2888
+ else {
2889
+ overlay.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
2890
+ if(!state) { $(this).hide(); }
2891
+ });
2892
+ }
2893
+
2894
+ // Reset position on hide
2895
+ if(!state) {
2896
+ overlay.queue(function(next) {
2897
+ overlay.css({ left: '', top: '' }).removeData('toggleState');
2898
+ next();
2899
+ });
2900
+ }
2901
+
2902
+ return self;
2903
+ },
2904
+
2905
+ show: function(event, duration) { return self.toggle(event, TRUE, duration); },
2906
+ hide: function(event, duration) { return self.toggle(event, FALSE, duration); },
2907
+
2908
+ destroy: function()
2909
+ {
2910
+ var delBlanket = overlay;
2911
+
2912
+ if(delBlanket) {
2913
+ // Check if any other modal tooltips are present
2914
+ delBlanket = $('[' + attr + ']').not(tooltip).length < 1;
2915
+
2916
+ // Remove overlay if needed
2917
+ if(delBlanket) {
2918
+ elems.overlay.remove();
2919
+ $(document).unbind(globalNamespace);
2920
+ }
2921
+ else {
2922
+ elems.overlay.unbind(globalNamespace+api.id);
2923
+ }
2924
+
2925
+ // Undelegate focus handler
2926
+ docBody.unbind('focusin'+namespace);
2927
+ }
2928
+
2929
+ // Remove bound events
2930
+ return tooltip.removeAttr(attr).unbind(globalNamespace);
2931
+ }
2932
+ });
2933
+
2934
+ self.init();
2935
+ }
2936
+
2937
+ PLUGINS.modal = function(api) {
2938
+ var self = api.plugins.modal;
2939
+
2940
+ return 'object' === typeof self ? self : (api.plugins.modal = new Modal(api));
2941
+ };
2942
+
2943
+ // Plugin needs to be initialized on render
2944
+ PLUGINS.modal.initialize = 'render';
2945
+
2946
+ // Setup sanitiztion rules
2947
+ PLUGINS.modal.sanitize = function(opts) {
2948
+ if(opts.show) {
2949
+ if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
2950
+ else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
2951
+ }
2952
+ };
2953
+
2954
+ // Base z-index for all modal tooltips (use qTip core z-index as a base)
2955
+ PLUGINS.modal.zindex = QTIP.zindex - 200;
2956
+
2957
+ // Defines the selector used to select all 'focusable' elements within the modal when using the show.modal.stealfocus option.
2958
+ // Selectors initially taken from http://stackoverflow.com/questions/7668525/is-there-a-jquery-selector-to-get-all-elements-that-can-get-focus
2959
+ PLUGINS.modal.focusable = ['a[href]', 'area[href]', 'input', 'select', 'textarea', 'button', 'iframe', 'object', 'embed', '[tabindex]', '[contenteditable]'];
2960
+
2961
+ // Extend original api defaults
2962
+ $.extend(TRUE, QTIP.defaults, {
2963
+ show: {
2964
+ modal: {
2965
+ on: FALSE,
2966
+ effect: TRUE,
2967
+ blur: TRUE,
2968
+ stealfocus: TRUE,
2969
+ escape: TRUE
2970
+ }
2971
+ }
2972
+ });
2973
+
2974
+
2975
+ PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
2976
+ {
2977
+ var target = posOptions.target,
2978
+ tooltip = api.elements.tooltip,
2979
+ my = posOptions.my,
2980
+ at = posOptions.at,
2981
+ adjust = posOptions.adjust,
2982
+ method = adjust.method.split(' '),
2983
+ methodX = method[0],
2984
+ methodY = method[1] || method[0],
2985
+ viewport = posOptions.viewport,
2986
+ container = posOptions.container,
2987
+ cache = api.cache,
2988
+ tip = api.plugins.tip,
2989
+ adjusted = { left: 0, top: 0 },
2990
+ fixed, newMy, newClass;
2991
+
2992
+ // If viewport is not a jQuery element, or it's the window/document or no adjustment method is used... return
2993
+ if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
2994
+ return adjusted;
2995
+ }
2996
+
2997
+ // Cache our viewport details
2998
+ fixed = tooltip.css('position') === 'fixed';
2999
+ viewport = {
3000
+ elem: viewport,
3001
+ height: viewport[ (viewport[0] === window ? 'h' : 'outerH') + 'eight' ](),
3002
+ width: viewport[ (viewport[0] === window ? 'w' : 'outerW') + 'idth' ](),
3003
+ scrollleft: fixed ? 0 : viewport.scrollLeft(),
3004
+ scrolltop: fixed ? 0 : viewport.scrollTop(),
3005
+ offset: viewport.offset() || { left: 0, top: 0 }
3006
+ };
3007
+ container = {
3008
+ elem: container,
3009
+ scrollLeft: container.scrollLeft(),
3010
+ scrollTop: container.scrollTop(),
3011
+ offset: container.offset() || { left: 0, top: 0 }
3012
+ };
3013
+
3014
+ // Generic calculation method
3015
+ function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) {
3016
+ var initialPos = position[side1],
3017
+ mySide = my[side], atSide = at[side],
3018
+ isShift = type === SHIFT,
3019
+ viewportScroll = -container.offset[side1] + viewport.offset[side1] + viewport['scroll'+side1],
3020
+ myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
3021
+ atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
3022
+ tipLength = tip && tip.size ? tip.size[lengthName] || 0 : 0,
3023
+ tipAdjust = tip && tip.corner && tip.corner.precedance === side && !isShift ? tipLength : 0,
3024
+ overflow1 = viewportScroll - initialPos + tipAdjust,
3025
+ overflow2 = initialPos + elemLength - viewport[lengthName] - viewportScroll + tipAdjust,
3026
+ offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);
3027
+
3028
+ // shift
3029
+ if(isShift) {
3030
+ tipAdjust = tip && tip.corner && tip.corner.precedance === otherSide ? tipLength : 0;
3031
+ offset = (mySide === side1 ? 1 : -1) * myLength - tipAdjust;
3032
+
3033
+ // Adjust position but keep it within viewport dimensions
3034
+ position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
3035
+ position[side1] = Math.max(
3036
+ -container.offset[side1] + viewport.offset[side1] + (tipAdjust && tip.corner[side] === CENTER ? tip.offset : 0),
3037
+ initialPos - offset,
3038
+ Math.min(
3039
+ Math.max(-container.offset[side1] + viewport.offset[side1] + viewport[lengthName], initialPos + offset),
3040
+ position[side1]
3041
+ )
3042
+ );
3043
+ }
3044
+
3045
+ // flip/flipinvert
3046
+ else {
3047
+ // Update adjustment amount depending on if using flipinvert or flip
3048
+ adjust *= (type === FLIPINVERT ? 2 : 0);
3049
+
3050
+ // Check for overflow on the left/top
3051
+ if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
3052
+ position[side1] -= offset + adjust;
3053
+ newMy['invert'+side](side1);
3054
+ }
3055
+
3056
+ // Check for overflow on the bottom/right
3057
+ else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) {
3058
+ position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;
3059
+ newMy['invert'+side](side2);
3060
+ }
3061
+
3062
+ // Make sure we haven't made things worse with the adjustment and reset if so
3063
+ if(position[side1] < viewportScroll && -position[side1] > overflow2) {
3064
+ position[side1] = initialPos; newMy = my.clone();
3065
+ }
3066
+ }
3067
+
3068
+ return position[side1] - initialPos;
3069
+ }
3070
+
3071
+ // Set newMy if using flip or flipinvert methods
3072
+ if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }
3073
+
3074
+ // Adjust position based onviewport and adjustment options
3075
+ adjusted = {
3076
+ left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
3077
+ top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0
3078
+ };
3079
+
3080
+ // Set tooltip position class if it's changed
3081
+ if(newMy && cache.lastClass !== (newClass = NAMESPACE + '-pos-' + newMy.abbrev())) {
3082
+ tooltip.removeClass(api.cache.lastClass).addClass( (api.cache.lastClass = newClass) );
3083
+ }
3084
+
3085
+ return adjusted;
3086
+ };
3087
+ PLUGINS.imagemap = function(api, area, corner, adjustMethod)
3088
+ {
3089
+ if(!area.jquery) { area = $(area); }
3090
+
3091
+ var cache = (api.cache.areas = {}),
3092
+ shape = (area[0].shape || area.attr('shape')).toLowerCase(),
3093
+ coordsString = area[0].coords || area.attr('coords'),
3094
+ baseCoords = coordsString.split(','),
3095
+ coords = [],
3096
+ image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
3097
+ imageOffset = image.offset(),
3098
+ result = {
3099
+ width: 0, height: 0,
3100
+ position: {
3101
+ top: 1e10, right: 0,
3102
+ bottom: 0, left: 1e10
3103
+ }
3104
+ },
3105
+ i = 0, next = 0, dimensions;
3106
+
3107
+ // POLY area coordinate calculator
3108
+ // Special thanks to Ed Cradock for helping out with this.
3109
+ // Uses a binary search algorithm to find suitable coordinates.
3110
+ function polyCoordinates(result, coords, corner)
3111
+ {
3112
+ var i = 0,
3113
+ compareX = 1, compareY = 1,
3114
+ realX = 0, realY = 0,
3115
+ newWidth = result.width,
3116
+ newHeight = result.height;
3117
+
3118
+ // Use a binary search algorithm to locate most suitable coordinate (hopefully)
3119
+ while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
3120
+ {
3121
+ newWidth = Math.floor(newWidth / 2);
3122
+ newHeight = Math.floor(newHeight / 2);
3123
+
3124
+ if(corner.x === LEFT){ compareX = newWidth; }
3125
+ else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
3126
+ else{ compareX += Math.floor(newWidth / 2); }
3127
+
3128
+ if(corner.y === TOP){ compareY = newHeight; }
3129
+ else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
3130
+ else{ compareY += Math.floor(newHeight / 2); }
3131
+
3132
+ i = coords.length; while(i--)
3133
+ {
3134
+ if(coords.length < 2){ break; }
3135
+
3136
+ realX = coords[i][0] - result.position.left;
3137
+ realY = coords[i][1] - result.position.top;
3138
+
3139
+ if((corner.x === LEFT && realX >= compareX) ||
3140
+ (corner.x === RIGHT && realX <= compareX) ||
3141
+ (corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) ||
3142
+ (corner.y === TOP && realY >= compareY) ||
3143
+ (corner.y === BOTTOM && realY <= compareY) ||
3144
+ (corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) {
3145
+ coords.splice(i, 1);
3146
+ }
3147
+ }
3148
+ }
3149
+
3150
+ return { left: coords[0][0], top: coords[0][1] };
3151
+ }
3152
+
3153
+ // Make sure we account for padding and borders on the image
3154
+ imageOffset.left += Math.ceil((image.outerWidth() - image.width()) / 2);
3155
+ imageOffset.top += Math.ceil((image.outerHeight() - image.height()) / 2);
3156
+
3157
+ // Parse coordinates into proper array
3158
+ if(shape === 'poly') {
3159
+ i = baseCoords.length; while(i--)
3160
+ {
3161
+ next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
3162
+
3163
+ if(next[0] > result.position.right){ result.position.right = next[0]; }
3164
+ if(next[0] < result.position.left){ result.position.left = next[0]; }
3165
+ if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
3166
+ if(next[1] < result.position.top){ result.position.top = next[1]; }
3167
+
3168
+ coords.push(next);
3169
+ }
3170
+ }
3171
+ else {
3172
+ i = -1; while(i++ < baseCoords.length) {
3173
+ coords.push( parseInt(baseCoords[i], 10) );
3174
+ }
3175
+ }
3176
+
3177
+ // Calculate details
3178
+ switch(shape)
3179
+ {
3180
+ case 'rect':
3181
+ result = {
3182
+ width: Math.abs(coords[2] - coords[0]),
3183
+ height: Math.abs(coords[3] - coords[1]),
3184
+ position: {
3185
+ left: Math.min(coords[0], coords[2]),
3186
+ top: Math.min(coords[1], coords[3])
3187
+ }
3188
+ };
3189
+ break;
3190
+
3191
+ case 'circle':
3192
+ result = {
3193
+ width: coords[2] + 2,
3194
+ height: coords[2] + 2,
3195
+ position: { left: coords[0], top: coords[1] }
3196
+ };
3197
+ break;
3198
+
3199
+ case 'poly':
3200
+ result.width = Math.abs(result.position.right - result.position.left);
3201
+ result.height = Math.abs(result.position.bottom - result.position.top);
3202
+
3203
+ if(corner.abbrev() === 'c') {
3204
+ result.position = {
3205
+ left: result.position.left + (result.width / 2),
3206
+ top: result.position.top + (result.height / 2)
3207
+ };
3208
+ }
3209
+ else {
3210
+ // Calculate if we can't find a cached value
3211
+ if(!cache[corner+coordsString]) {
3212
+ result.position = polyCoordinates(result, coords.slice(), corner);
3213
+
3214
+ // If flip adjustment is enabled, also calculate the closest opposite point
3215
+ if(adjustMethod && (adjustMethod[0] === 'flip' || adjustMethod[1] === 'flip')) {
3216
+ result.offset = polyCoordinates(result, coords.slice(), {
3217
+ x: corner.x === LEFT ? RIGHT : corner.x === RIGHT ? LEFT : CENTER,
3218
+ y: corner.y === TOP ? BOTTOM : corner.y === BOTTOM ? TOP : CENTER
3219
+ });
3220
+
3221
+ result.offset.left -= result.position.left;
3222
+ result.offset.top -= result.position.top;
3223
+ }
3224
+
3225
+ // Store the result
3226
+ cache[corner+coordsString] = result;
3227
+ }
3228
+
3229
+ // Grab the cached result
3230
+ result = cache[corner+coordsString];
3231
+ }
3232
+
3233
+ result.width = result.height = 0;
3234
+ break;
3235
+ }
3236
+
3237
+ // Add image position to offset coordinates
3238
+ result.position.left += imageOffset.left;
3239
+ result.position.top += imageOffset.top;
3240
+
3241
+ return result;
3242
+ };
3243
+
3244
+
3245
+ /*
3246
+ * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
3247
+ * Special thanks to Brandon Aaron
3248
+ */
3249
+ function IE6(api)
3250
+ {
3251
+ var self = this,
3252
+ elems = api.elements,
3253
+ options = api.options,
3254
+ tooltip = elems.tooltip,
3255
+ namespace = '.ie6-' + api.id,
3256
+ bgiframe = $('select, object').length < 1,
3257
+ isDrawing = 0,
3258
+ modalProcessed = FALSE,
3259
+ redrawContainer;
3260
+
3261
+ api.checks.ie6 = {
3262
+ '^content|style$': function(obj, o, v){ redraw(); }
3263
+ };
3264
+
3265
+ $.extend(self, {
3266
+ init: function()
3267
+ {
3268
+ var win = $(window), scroll;
3269
+
3270
+ // Create the BGIFrame element if needed
3271
+ if(bgiframe) {
3272
+ elems.bgiframe = $('<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
3273
+ ' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
3274
+ '-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>');
3275
+
3276
+ // Append the new element to the tooltip
3277
+ elems.bgiframe.appendTo(tooltip);
3278
+
3279
+ // Update BGIFrame on tooltip move
3280
+ tooltip.bind('tooltipmove'+namespace, self.adjustBGIFrame);
3281
+ }
3282
+
3283
+ // redraw() container for width/height calculations
3284
+ redrawContainer = $('<div/>', { id: 'qtip-rcontainer' })
3285
+ .appendTo(document.body);
3286
+
3287
+ // Set dimensions
3288
+ self.redraw();
3289
+
3290
+ // Fixup modal plugin if present too
3291
+ if(elems.overlay && !modalProcessed) {
3292
+ scroll = function() {
3293
+ elems.overlay[0].style.top = win.scrollTop() + 'px';
3294
+ };
3295
+ win.bind('scroll.qtip-ie6, resize.qtip-ie6', scroll);
3296
+ scroll(); // Fire it initially too
3297
+
3298
+ elems.overlay.addClass('qtipmodal-ie6fix'); // Add fix class
3299
+
3300
+ modalProcessed = TRUE; // Set flag
3301
+ }
3302
+ },
3303
+
3304
+ adjustBGIFrame: function()
3305
+ {
3306
+ var dimensions = api.get('dimensions'), // Determine current tooltip dimensions
3307
+ plugin = api.plugins.tip,
3308
+ tip = elems.tip,
3309
+ tipAdjust, offset;
3310
+
3311
+ // Adjust border offset
3312
+ offset = parseInt(tooltip.css('border-left-width'), 10) || 0;
3313
+ offset = { left: -offset, top: -offset };
3314
+
3315
+ // Adjust for tips plugin
3316
+ if(plugin && tip) {
3317
+ tipAdjust = (plugin.corner.precedance === 'x') ? ['width', 'left'] : ['height', 'top'];
3318
+ offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
3319
+ }
3320
+
3321
+ // Update bgiframe
3322
+ elems.bgiframe.css(offset).css(dimensions);
3323
+ },
3324
+
3325
+ // Max/min width simulator function
3326
+ redraw: function()
3327
+ {
3328
+ if(api.rendered < 1 || isDrawing) { return self; }
3329
+
3330
+ var style = options.style,
3331
+ container = options.position.container,
3332
+ perc, width, max, min;
3333
+
3334
+ // Set drawing flag
3335
+ isDrawing = 1;
3336
+
3337
+ // If tooltip has a set height/width, just set it... like a boss!
3338
+ if(style.height) { tooltip.css(HEIGHT, style.height); }
3339
+ if(style.width) { tooltip.css(WIDTH, style.width); }
3340
+
3341
+ // Simulate max/min width if not set width present...
3342
+ else {
3343
+ // Reset width and add fluid class
3344
+ tooltip.css(WIDTH, '').appendTo(redrawContainer);
3345
+
3346
+ // Grab our tooltip width (add 1 if odd so we don't get wrapping problems.. huzzah!)
3347
+ width = tooltip.width();
3348
+ if(width % 2 < 1) { width += 1; }
3349
+
3350
+ // Grab our max/min properties
3351
+ max = tooltip.css('max-width') || '';
3352
+ min = tooltip.css('min-width') || '';
3353
+
3354
+ // Parse into proper pixel values
3355
+ perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
3356
+ max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
3357
+ min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;
3358
+
3359
+ // Determine new dimension size based on max/min/current values
3360
+ width = max + min ? Math.min(Math.max(width, min), max) : width;
3361
+
3362
+ // Set the newly calculated width and remvoe fluid class
3363
+ tooltip.css(WIDTH, Math.round(width)).appendTo(container);
3364
+ }
3365
+
3366
+ // Set drawing flag
3367
+ isDrawing = 0;
3368
+
3369
+ return self;
3370
+ },
3371
+
3372
+ destroy: function()
3373
+ {
3374
+ // Remove iframe
3375
+ if(bgiframe) { elems.bgiframe.remove(); }
3376
+
3377
+ // Remove bound events
3378
+ tooltip.unbind(namespace);
3379
+ }
3380
+ });
3381
+
3382
+ self.init();
3383
+ }
3384
+
3385
+ PLUGINS.ie6 = function(api)
3386
+ {
3387
+ var browser = $.browser,
3388
+ self = api.plugins.ie6;
3389
+
3390
+ // Proceed only if the browser is IE6
3391
+ if(!(browser.msie && (''+browser.version).charAt(0) === '6')) {
3392
+ return FALSE;
3393
+ }
3394
+
3395
+ return 'object' === typeof self ? self : (api.plugins.ie6 = new IE6(api));
3396
+ };
3397
+
3398
+ // Plugin needs to be initialized on render
3399
+ PLUGINS.ie6.initialize = 'render';
3400
+
3401
+
3402
+ }));
3403
+ }( window, document ));