stuff_to_do_plugin 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. data/COPYRIGHT.txt +18 -0
  2. data/CREDITS.txt +6 -0
  3. data/GPL.txt +339 -0
  4. data/README.rdoc +61 -0
  5. data/Rakefile +38 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/stuff_to_do_controller.rb +161 -0
  8. data/app/helpers/stuff_to_do_helper.rb +88 -0
  9. data/app/models/stuff_to_do.rb +208 -0
  10. data/app/models/stuff_to_do_filter.rb +32 -0
  11. data/app/models/stuff_to_do_mailer.rb +16 -0
  12. data/app/views/settings/_stuff_to_do_settings.html.erb +27 -0
  13. data/app/views/stuff_to_do/_issue.html.erb +16 -0
  14. data/app/views/stuff_to_do/_item.html.erb +5 -0
  15. data/app/views/stuff_to_do/_left_panes.html.erb +51 -0
  16. data/app/views/stuff_to_do/_panes.html.erb +11 -0
  17. data/app/views/stuff_to_do/_project.html.erb +6 -0
  18. data/app/views/stuff_to_do/_right_panes.html.erb +25 -0
  19. data/app/views/stuff_to_do/_time_grid.html.erb +113 -0
  20. data/app/views/stuff_to_do/_time_grid_form.html.erb +32 -0
  21. data/app/views/stuff_to_do/index.html.erb +44 -0
  22. data/app/views/stuff_to_do_mailer/recommended_below_threshold.erb +3 -0
  23. data/app/views/stuff_to_do_mailer/recommended_below_threshold.text.html.rhtml +1 -0
  24. data/assets/images/b.png +0 -0
  25. data/assets/images/bl.png +0 -0
  26. data/assets/images/br.png +0 -0
  27. data/assets/images/closelabel.gif +0 -0
  28. data/assets/images/loading.gif +0 -0
  29. data/assets/images/tl.png +0 -0
  30. data/assets/images/tr.png +0 -0
  31. data/assets/javascripts/facebox.js +319 -0
  32. data/assets/javascripts/jquery-1.2.6.min.js +32 -0
  33. data/assets/javascripts/jquery-ui.js +2839 -0
  34. data/assets/javascripts/jquery.contextMenu.js +212 -0
  35. data/assets/javascripts/semantic.cache +15 -0
  36. data/assets/javascripts/stuff-to-do.js +270 -0
  37. data/assets/javascripts/ui/build.xml +24 -0
  38. data/assets/javascripts/ui/effects.blind.js +49 -0
  39. data/assets/javascripts/ui/effects.bounce.js +78 -0
  40. data/assets/javascripts/ui/effects.clip.js +54 -0
  41. data/assets/javascripts/ui/effects.core.js +510 -0
  42. data/assets/javascripts/ui/effects.drop.js +50 -0
  43. data/assets/javascripts/ui/effects.explode.js +79 -0
  44. data/assets/javascripts/ui/effects.fold.js +55 -0
  45. data/assets/javascripts/ui/effects.highlight.js +48 -0
  46. data/assets/javascripts/ui/effects.pulsate.js +55 -0
  47. data/assets/javascripts/ui/effects.scale.js +180 -0
  48. data/assets/javascripts/ui/effects.shake.js +57 -0
  49. data/assets/javascripts/ui/effects.slide.js +50 -0
  50. data/assets/javascripts/ui/effects.transfer.js +59 -0
  51. data/assets/javascripts/ui/i18n/ui.datepicker-ar.js +26 -0
  52. data/assets/javascripts/ui/i18n/ui.datepicker-bg.js +25 -0
  53. data/assets/javascripts/ui/i18n/ui.datepicker-ca.js +25 -0
  54. data/assets/javascripts/ui/i18n/ui.datepicker-cs.js +25 -0
  55. data/assets/javascripts/ui/i18n/ui.datepicker-da.js +25 -0
  56. data/assets/javascripts/ui/i18n/ui.datepicker-de.js +25 -0
  57. data/assets/javascripts/ui/i18n/ui.datepicker-eo.js +25 -0
  58. data/assets/javascripts/ui/i18n/ui.datepicker-es.js +25 -0
  59. data/assets/javascripts/ui/i18n/ui.datepicker-fa.js +25 -0
  60. data/assets/javascripts/ui/i18n/ui.datepicker-fi.js +25 -0
  61. data/assets/javascripts/ui/i18n/ui.datepicker-fr.js +25 -0
  62. data/assets/javascripts/ui/i18n/ui.datepicker-he.js +25 -0
  63. data/assets/javascripts/ui/i18n/ui.datepicker-hr.js +25 -0
  64. data/assets/javascripts/ui/i18n/ui.datepicker-hu.js +25 -0
  65. data/assets/javascripts/ui/i18n/ui.datepicker-hy.js +25 -0
  66. data/assets/javascripts/ui/i18n/ui.datepicker-id.js +25 -0
  67. data/assets/javascripts/ui/i18n/ui.datepicker-is.js +25 -0
  68. data/assets/javascripts/ui/i18n/ui.datepicker-it.js +25 -0
  69. data/assets/javascripts/ui/i18n/ui.datepicker-ja.js +26 -0
  70. data/assets/javascripts/ui/i18n/ui.datepicker-ko.js +25 -0
  71. data/assets/javascripts/ui/i18n/ui.datepicker-lt.js +25 -0
  72. data/assets/javascripts/ui/i18n/ui.datepicker-lv.js +25 -0
  73. data/assets/javascripts/ui/i18n/ui.datepicker-nl.js +25 -0
  74. data/assets/javascripts/ui/i18n/ui.datepicker-no.js +25 -0
  75. data/assets/javascripts/ui/i18n/ui.datepicker-pl.js +25 -0
  76. data/assets/javascripts/ui/i18n/ui.datepicker-pt-BR.js +25 -0
  77. data/assets/javascripts/ui/i18n/ui.datepicker-ro.js +25 -0
  78. data/assets/javascripts/ui/i18n/ui.datepicker-ru.js +25 -0
  79. data/assets/javascripts/ui/i18n/ui.datepicker-sk.js +25 -0
  80. data/assets/javascripts/ui/i18n/ui.datepicker-sl.js +26 -0
  81. data/assets/javascripts/ui/i18n/ui.datepicker-sq.js +25 -0
  82. data/assets/javascripts/ui/i18n/ui.datepicker-sv.js +25 -0
  83. data/assets/javascripts/ui/i18n/ui.datepicker-th.js +25 -0
  84. data/assets/javascripts/ui/i18n/ui.datepicker-tr.js +25 -0
  85. data/assets/javascripts/ui/i18n/ui.datepicker-uk.js +25 -0
  86. data/assets/javascripts/ui/i18n/ui.datepicker-zh-CN.js +25 -0
  87. data/assets/javascripts/ui/i18n/ui.datepicker-zh-TW.js +25 -0
  88. data/assets/javascripts/ui/svn.log +11 -0
  89. data/assets/javascripts/ui/ui.accordion.js +400 -0
  90. data/assets/javascripts/ui/ui.core.js +533 -0
  91. data/assets/javascripts/ui/ui.datepicker.js +1754 -0
  92. data/assets/javascripts/ui/ui.dialog.js +630 -0
  93. data/assets/javascripts/ui/ui.draggable.js +696 -0
  94. data/assets/javascripts/ui/ui.droppable.js +314 -0
  95. data/assets/javascripts/ui/ui.progressbar.js +114 -0
  96. data/assets/javascripts/ui/ui.resizable.js +805 -0
  97. data/assets/javascripts/ui/ui.selectable.js +266 -0
  98. data/assets/javascripts/ui/ui.slider.js +552 -0
  99. data/assets/javascripts/ui/ui.sortable.js +1012 -0
  100. data/assets/javascripts/ui/ui.tabs.js +572 -0
  101. data/assets/stylesheets/stuff_to_do.css +216 -0
  102. data/config/locales/bg.yml +18 -0
  103. data/config/locales/ca-fr.yml +18 -0
  104. data/config/locales/cs.yml +16 -0
  105. data/config/locales/da.yml +16 -0
  106. data/config/locales/de.yml +18 -0
  107. data/config/locales/en.yml +24 -0
  108. data/config/locales/es.yml +19 -0
  109. data/config/locales/fr.yml +17 -0
  110. data/config/locales/hu.yml +16 -0
  111. data/config/locales/it.yml +16 -0
  112. data/config/locales/ja.yml +18 -0
  113. data/config/locales/ko.yml +18 -0
  114. data/config/locales/lt.yml +18 -0
  115. data/config/locales/nl.yml +20 -0
  116. data/config/locales/pt-BR.yml +18 -0
  117. data/config/locales/ru.yml +19 -0
  118. data/config/locales/sv.yml +19 -0
  119. data/config/locales/tr.yml +18 -0
  120. data/config/routes.rb +3 -0
  121. data/init.rb +54 -0
  122. data/lang/bg.yml +17 -0
  123. data/lang/ca-fr.yml +17 -0
  124. data/lang/cs.yml +15 -0
  125. data/lang/da.yml +15 -0
  126. data/lang/de.yml +17 -0
  127. data/lang/en.yml +21 -0
  128. data/lang/es.yml +18 -0
  129. data/lang/fr.yml +16 -0
  130. data/lang/hu.yml +15 -0
  131. data/lang/it.yml +15 -0
  132. data/lang/ja.yml +17 -0
  133. data/lang/ko.yml +17 -0
  134. data/lang/lt.yml +17 -0
  135. data/lang/pt-br.yml +17 -0
  136. data/lang/ru.yml +15 -0
  137. data/lang/sv.yml +18 -0
  138. data/lang/tr.yml +17 -0
  139. data/lib/redmine_stuff_to_do/stuff_to_do_compatibility.rb +15 -0
  140. data/lib/stuff_to_do_array_patch.rb +8 -0
  141. data/lib/stuff_to_do_issue_patch.rb +57 -0
  142. data/lib/stuff_to_do_project_patch.rb +31 -0
  143. data/lib/stuff_to_do_user_patch.rb +10 -0
  144. data/rails/init.rb +1 -0
  145. data/spec/controllers/stuff_to_do_controller_add_to_time_grid_spec.rb +58 -0
  146. data/spec/controllers/stuff_to_do_controller_index_spec.rb +155 -0
  147. data/spec/controllers/stuff_to_do_controller_remove_from_time_grid_spec.rb +56 -0
  148. data/spec/controllers/stuff_to_do_controller_reorder_spec.rb +179 -0
  149. data/spec/controllers/stuff_to_do_controller_save_time_entries_spec.rb +56 -0
  150. data/spec/controllers/stuff_to_do_private_methods_spec.rb +82 -0
  151. data/spec/lib/stuff_to_do_issue_patch_spec.rb +60 -0
  152. data/spec/lib/stuff_to_do_project_patch_spec.rb +50 -0
  153. data/spec/lib/stuff_to_do_user_patch_spec.rb +8 -0
  154. data/spec/models/stuff_to_do_filter_spec.rb +3 -0
  155. data/spec/models/stuff_to_do_mailer_spec.rb +42 -0
  156. data/spec/models/stuff_to_do_spec.rb +426 -0
  157. data/spec/sanity_spec.rb +7 -0
  158. data/spec/spec_helper.rb +130 -0
  159. metadata +211 -0
@@ -0,0 +1,572 @@
1
+ /*
2
+ * jQuery UI Tabs @VERSION
3
+ *
4
+ * Copyright (c) 2008 AUTHORS.txt (http://ui.jquery.com/about)
5
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
6
+ * and GPL (GPL-LICENSE.txt) licenses.
7
+ *
8
+ * http://docs.jquery.com/UI/Tabs
9
+ *
10
+ * Depends:
11
+ * ui.core.js
12
+ */
13
+ (function($) {
14
+
15
+ $.widget("ui.tabs", {
16
+
17
+ _init: function() {
18
+ // create tabs
19
+ this._tabify(true);
20
+ },
21
+
22
+ destroy: function() {
23
+ var o = this.options;
24
+ this.element.unbind('.tabs')
25
+ .removeClass(o.navClass).removeData('tabs');
26
+ this.$tabs.each(function() {
27
+ var href = $.data(this, 'href.tabs');
28
+ if (href)
29
+ this.href = href;
30
+ var $this = $(this).unbind('.tabs');
31
+ $.each(['href', 'load', 'cache'], function(i, prefix) {
32
+ $this.removeData(prefix + '.tabs');
33
+ });
34
+ });
35
+ this.$lis.add(this.$panels).each(function() {
36
+ if ($.data(this, 'destroy.tabs'))
37
+ $(this).remove();
38
+ else
39
+ $(this).removeClass([o.selectedClass, o.deselectableClass,
40
+ o.disabledClass, o.panelClass, o.hideClass].join(' '));
41
+ });
42
+ if (o.cookie)
43
+ this._cookie(null, o.cookie);
44
+ },
45
+
46
+ _setData: function(key, value) {
47
+ if ((/^selected/).test(key))
48
+ this.select(value);
49
+ else {
50
+ this.options[key] = value;
51
+ this._tabify();
52
+ }
53
+ },
54
+
55
+ length: function() {
56
+ return this.$tabs.length;
57
+ },
58
+
59
+ _tabId: function(a) {
60
+ return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '')
61
+ || this.options.idPrefix + $.data(a);
62
+ },
63
+
64
+ _sanitizeSelector: function(hash) {
65
+ return hash.replace(/:/g, '\\:'); // we need this because an id may contain a ":"
66
+ },
67
+
68
+ _cookie: function() {
69
+ var cookie = this.cookie || (this.cookie = 'ui-tabs-' + $.data(this.element[0]));
70
+ return $.cookie.apply(null, [cookie].concat($.makeArray(arguments)));
71
+ },
72
+
73
+ _tabify: function(init) {
74
+
75
+ this.$lis = $('li:has(a[href])', this.element);
76
+ this.$tabs = this.$lis.map(function() { return $('a', this)[0]; });
77
+ this.$panels = $([]);
78
+
79
+ var self = this, o = this.options;
80
+
81
+ this.$tabs.each(function(i, a) {
82
+ // inline tab
83
+ if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash
84
+ self.$panels = self.$panels.add(self._sanitizeSelector(a.hash));
85
+ // remote tab
86
+ else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#"
87
+ $.data(a, 'href.tabs', a.href); // required for restore on destroy
88
+ $.data(a, 'load.tabs', a.href); // mutable
89
+ var id = self._tabId(a);
90
+ a.href = '#' + id;
91
+ var $panel = $('#' + id);
92
+ if (!$panel.length) {
93
+ $panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass)
94
+ .insertAfter(self.$panels[i - 1] || self.element);
95
+ $panel.data('destroy.tabs', true);
96
+ }
97
+ self.$panels = self.$panels.add($panel);
98
+ }
99
+ // invalid tab href
100
+ else
101
+ o.disabled.push(i + 1);
102
+ });
103
+
104
+ // initialization from scratch
105
+ if (init) {
106
+
107
+ // attach necessary classes for styling if not present
108
+ this.element.addClass(o.navClass);
109
+ this.$panels.addClass(o.panelClass);
110
+
111
+ // Selected tab
112
+ // use "selected" option or try to retrieve:
113
+ // 1. from fragment identifier in url
114
+ // 2. from cookie
115
+ // 3. from selected class attribute on <li>
116
+ if (o.selected === undefined) {
117
+ if (location.hash) {
118
+ this.$tabs.each(function(i, a) {
119
+ if (a.hash == location.hash) {
120
+ o.selected = i;
121
+ return false; // break
122
+ }
123
+ });
124
+ }
125
+ else if (o.cookie) {
126
+ var index = parseInt(self._cookie(), 10);
127
+ if (index && self.$tabs[index]) o.selected = index;
128
+ }
129
+ else if (self.$lis.filter('.' + o.selectedClass).length)
130
+ o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] );
131
+ }
132
+ o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default
133
+
134
+ // Take disabling tabs via class attribute from HTML
135
+ // into account and update option properly.
136
+ // A selected tab cannot become disabled.
137
+ o.disabled = $.unique(o.disabled.concat(
138
+ $.map(this.$lis.filter('.' + o.disabledClass),
139
+ function(n, i) { return self.$lis.index(n); } )
140
+ )).sort();
141
+ if ($.inArray(o.selected, o.disabled) != -1)
142
+ o.disabled.splice($.inArray(o.selected, o.disabled), 1);
143
+
144
+ // highlight selected tab
145
+ this.$panels.addClass(o.hideClass);
146
+ this.$lis.removeClass(o.selectedClass);
147
+ if (o.selected !== null) {
148
+ this.$panels.eq(o.selected).removeClass(o.hideClass);
149
+ var classes = [o.selectedClass];
150
+ if (o.deselectable) classes.push(o.deselectableClass);
151
+ this.$lis.eq(o.selected).addClass(classes.join(' '));
152
+
153
+ // seems to be expected behavior that the show callback is fired
154
+ var onShow = function() {
155
+ self._trigger('show', null,
156
+ self.ui(self.$tabs[o.selected], self.$panels[o.selected]));
157
+ };
158
+
159
+ // load if remote tab
160
+ if ($.data(this.$tabs[o.selected], 'load.tabs'))
161
+ this.load(o.selected, onShow);
162
+ // just trigger show event
163
+ else onShow();
164
+ }
165
+
166
+ // clean up to avoid memory leaks in certain versions of IE 6
167
+ $(window).bind('unload', function() {
168
+ self.$tabs.unbind('.tabs');
169
+ self.$lis = self.$tabs = self.$panels = null;
170
+ });
171
+
172
+ }
173
+ // update selected after add/remove
174
+ else
175
+ o.selected = this.$lis.index( this.$lis.filter('.' + o.selectedClass)[0] );
176
+
177
+ // set or update cookie after init and add/remove respectively
178
+ if (o.cookie) this._cookie(o.selected, o.cookie);
179
+
180
+ // disable tabs
181
+ for (var i = 0, li; li = this.$lis[i]; i++)
182
+ $(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass);
183
+
184
+ // reset cache if switching from cached to not cached
185
+ if (o.cache === false) this.$tabs.removeData('cache.tabs');
186
+
187
+ // set up animations
188
+ var hideFx, showFx;
189
+ if (o.fx) {
190
+ if (o.fx.constructor == Array) {
191
+ hideFx = o.fx[0];
192
+ showFx = o.fx[1];
193
+ }
194
+ else hideFx = showFx = o.fx;
195
+ }
196
+
197
+ // Reset certain styles left over from animation
198
+ // and prevent IE's ClearType bug...
199
+ function resetStyle($el, fx) {
200
+ $el.css({ display: '' });
201
+ if ($.browser.msie && fx.opacity) $el[0].style.removeAttribute('filter');
202
+ }
203
+
204
+ // Show a tab...
205
+ var showTab = showFx ?
206
+ function(clicked, $show) {
207
+ $show.animate(showFx, showFx.duration || 'normal', function() {
208
+ $show.removeClass(o.hideClass);
209
+ resetStyle($show, showFx);
210
+ self._trigger('show', null, self.ui(clicked, $show[0]));
211
+ });
212
+ } :
213
+ function(clicked, $show) {
214
+ $show.removeClass(o.hideClass);
215
+ self._trigger('show', null, self.ui(clicked, $show[0]));
216
+ };
217
+
218
+ // Hide a tab, $show is optional...
219
+ var hideTab = hideFx ?
220
+ function(clicked, $hide, $show) {
221
+ $hide.animate(hideFx, hideFx.duration || 'normal', function() {
222
+ $hide.addClass(o.hideClass);
223
+ resetStyle($hide, hideFx);
224
+ if ($show) showTab(clicked, $show, $hide);
225
+ });
226
+ } :
227
+ function(clicked, $hide, $show) {
228
+ $hide.addClass(o.hideClass);
229
+ if ($show) showTab(clicked, $show);
230
+ };
231
+
232
+ // Switch a tab...
233
+ function switchTab(clicked, $li, $hide, $show) {
234
+ var classes = [o.selectedClass];
235
+ if (o.deselectable) classes.push(o.deselectableClass);
236
+ $li.addClass(classes.join(' ')).siblings().removeClass(classes.join(' '));
237
+ hideTab(clicked, $hide, $show);
238
+ }
239
+
240
+ // attach tab event handler, unbind to avoid duplicates from former tabifying...
241
+ this.$tabs.unbind('.tabs').bind(o.event + '.tabs', function() {
242
+
243
+ //var trueClick = event.clientX; // add to history only if true click occured, not a triggered click
244
+ var $li = $(this).parents('li:eq(0)'),
245
+ $hide = self.$panels.filter(':visible'),
246
+ $show = $(self._sanitizeSelector(this.hash));
247
+
248
+ // If tab is already selected and not deselectable or tab disabled or
249
+ // or is already loading or click callback returns false stop here.
250
+ // Check if click handler returns false last so that it is not executed
251
+ // for a disabled or loading tab!
252
+ if (($li.hasClass(o.selectedClass) && !o.deselectable)
253
+ || $li.hasClass(o.disabledClass)
254
+ || $(this).hasClass(o.loadingClass)
255
+ || self._trigger('select', null, self.ui(this, $show[0])) === false
256
+ ) {
257
+ this.blur();
258
+ return false;
259
+ }
260
+
261
+ o.selected = self.$tabs.index(this);
262
+
263
+ // if tab may be closed
264
+ if (o.deselectable) {
265
+ if ($li.hasClass(o.selectedClass)) {
266
+ self.options.selected = null;
267
+ $li.removeClass([o.selectedClass, o.deselectableClass].join(' '));
268
+ self.$panels.stop();
269
+ hideTab(this, $hide);
270
+ this.blur();
271
+ return false;
272
+ } else if (!$hide.length) {
273
+ self.$panels.stop();
274
+ var a = this;
275
+ self.load(self.$tabs.index(this), function() {
276
+ $li.addClass([o.selectedClass, o.deselectableClass].join(' '));
277
+ showTab(a, $show);
278
+ });
279
+ this.blur();
280
+ return false;
281
+ }
282
+ }
283
+
284
+ if (o.cookie) self._cookie(o.selected, o.cookie);
285
+
286
+ // stop possibly running animations
287
+ self.$panels.stop();
288
+
289
+ // show new tab
290
+ if ($show.length) {
291
+ var a = this;
292
+ self.load(self.$tabs.index(this), $hide.length ?
293
+ function() {
294
+ switchTab(a, $li, $hide, $show);
295
+ } :
296
+ function() {
297
+ $li.addClass(o.selectedClass);
298
+ showTab(a, $show);
299
+ }
300
+ );
301
+ } else
302
+ throw 'jQuery UI Tabs: Mismatching fragment identifier.';
303
+
304
+ // Prevent IE from keeping other link focussed when using the back button
305
+ // and remove dotted border from clicked link. This is controlled via CSS
306
+ // in modern browsers; blur() removes focus from address bar in Firefox
307
+ // which can become a usability and annoying problem with tabs('rotate').
308
+ if ($.browser.msie) this.blur();
309
+
310
+ return false;
311
+
312
+ });
313
+
314
+ // disable click if event is configured to something else
315
+ if (o.event != 'click') this.$tabs.bind('click.tabs', function(){return false;});
316
+
317
+ },
318
+
319
+ add: function(url, label, index) {
320
+ if (index == undefined)
321
+ index = this.$tabs.length; // append by default
322
+
323
+ var o = this.options;
324
+ var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label));
325
+ $li.data('destroy.tabs', true);
326
+
327
+ var id = url.indexOf('#') == 0 ? url.replace('#', '') : this._tabId( $('a:first-child', $li)[0] );
328
+
329
+ // try to find an existing element before creating a new one
330
+ var $panel = $('#' + id);
331
+ if (!$panel.length) {
332
+ $panel = $(o.panelTemplate).attr('id', id)
333
+ .addClass(o.hideClass)
334
+ .data('destroy.tabs', true);
335
+ }
336
+ $panel.addClass(o.panelClass);
337
+ if (index >= this.$lis.length) {
338
+ $li.appendTo(this.element);
339
+ $panel.appendTo(this.element[0].parentNode);
340
+ } else {
341
+ $li.insertBefore(this.$lis[index]);
342
+ $panel.insertBefore(this.$panels[index]);
343
+ }
344
+
345
+ o.disabled = $.map(o.disabled,
346
+ function(n, i) { return n >= index ? ++n : n });
347
+
348
+ this._tabify();
349
+
350
+ if (this.$tabs.length == 1) {
351
+ $li.addClass(o.selectedClass);
352
+ $panel.removeClass(o.hideClass);
353
+ var href = $.data(this.$tabs[0], 'load.tabs');
354
+ if (href)
355
+ this.load(index, href);
356
+ }
357
+
358
+ // callback
359
+ this._trigger('add', null, this.ui(this.$tabs[index], this.$panels[index]));
360
+ },
361
+
362
+ remove: function(index) {
363
+ var o = this.options, $li = this.$lis.eq(index).remove(),
364
+ $panel = this.$panels.eq(index).remove();
365
+
366
+ // If selected tab was removed focus tab to the right or
367
+ // in case the last tab was removed the tab to the left.
368
+ if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1)
369
+ this.select(index + (index + 1 < this.$tabs.length ? 1 : -1));
370
+
371
+ o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
372
+ function(n, i) { return n >= index ? --n : n });
373
+
374
+ this._tabify();
375
+
376
+ // callback
377
+ this._trigger('remove', null, this.ui($li.find('a')[0], $panel[0]));
378
+ },
379
+
380
+ enable: function(index) {
381
+ var o = this.options;
382
+ if ($.inArray(index, o.disabled) == -1)
383
+ return;
384
+
385
+ var $li = this.$lis.eq(index).removeClass(o.disabledClass);
386
+ if ($.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2...
387
+ $li.css('display', 'inline-block');
388
+ setTimeout(function() {
389
+ $li.css('display', 'block');
390
+ }, 0);
391
+ }
392
+
393
+ o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });
394
+
395
+ // callback
396
+ this._trigger('enable', null, this.ui(this.$tabs[index], this.$panels[index]));
397
+ },
398
+
399
+ disable: function(index) {
400
+ var self = this, o = this.options;
401
+ if (index != o.selected) { // cannot disable already selected tab
402
+ this.$lis.eq(index).addClass(o.disabledClass);
403
+
404
+ o.disabled.push(index);
405
+ o.disabled.sort();
406
+
407
+ // callback
408
+ this._trigger('disable', null, this.ui(this.$tabs[index], this.$panels[index]));
409
+ }
410
+ },
411
+
412
+ select: function(index) {
413
+ // TODO make null as argument work
414
+ if (typeof index == 'string')
415
+ index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] );
416
+ this.$tabs.eq(index).trigger(this.options.event + '.tabs');
417
+ },
418
+
419
+ load: function(index, callback) { // callback is for internal usage only
420
+
421
+ var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0],
422
+ bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs');
423
+
424
+ callback = callback || function() {};
425
+
426
+ // no remote or from cache - just finish with callback
427
+ if (!url || !bypassCache && $.data(a, 'cache.tabs')) {
428
+ callback();
429
+ return;
430
+ }
431
+
432
+ // load remote from here on
433
+
434
+ var inner = function(parent) {
435
+ var $parent = $(parent), $inner = $parent.find('*:last');
436
+ return $inner.length && $inner.is(':not(img)') && $inner || $parent;
437
+ };
438
+ var cleanup = function() {
439
+ self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass)
440
+ .each(function() {
441
+ if (o.spinner)
442
+ inner(this).parent().html(inner(this).data('label.tabs'));
443
+ });
444
+ self.xhr = null;
445
+ };
446
+
447
+ if (o.spinner) {
448
+ var label = inner(a).html();
449
+ inner(a).wrapInner('<em></em>')
450
+ .find('em').data('label.tabs', label).html(o.spinner);
451
+ }
452
+
453
+ var ajaxOptions = $.extend({}, o.ajaxOptions, {
454
+ url: url,
455
+ success: function(r, s) {
456
+ $(self._sanitizeSelector(a.hash)).html(r);
457
+ cleanup();
458
+
459
+ if (o.cache)
460
+ $.data(a, 'cache.tabs', true); // if loaded once do not load them again
461
+
462
+ // callbacks
463
+ self._trigger('load', null, self.ui(self.$tabs[index], self.$panels[index]));
464
+ try {
465
+ o.ajaxOptions.success(r, s);
466
+ }
467
+ catch (event) {}
468
+
469
+ // This callback is required because the switch has to take
470
+ // place after loading has completed. Call last in order to
471
+ // fire load before show callback...
472
+ callback();
473
+ }
474
+ });
475
+ if (this.xhr) {
476
+ // terminate pending requests from other tabs and restore tab label
477
+ this.xhr.abort();
478
+ cleanup();
479
+ }
480
+ $a.addClass(o.loadingClass);
481
+ self.xhr = $.ajax(ajaxOptions);
482
+ },
483
+
484
+ url: function(index, url) {
485
+ this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url);
486
+ },
487
+
488
+ ui: function(tab, panel) {
489
+ return {
490
+ options: this.options,
491
+ tab: tab,
492
+ panel: panel,
493
+ index: this.$tabs.index(tab)
494
+ };
495
+ }
496
+
497
+ });
498
+
499
+ $.extend($.ui.tabs, {
500
+ version: '@VERSION',
501
+ getter: 'length',
502
+ defaults: {
503
+ ajaxOptions: null,
504
+ cache: false,
505
+ cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
506
+ deselectable: false,
507
+ deselectableClass: 'ui-tabs-deselectable',
508
+ disabled: [],
509
+ disabledClass: 'ui-tabs-disabled',
510
+ event: 'click',
511
+ fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
512
+ hideClass: 'ui-tabs-hide',
513
+ idPrefix: 'ui-tabs-',
514
+ loadingClass: 'ui-tabs-loading',
515
+ navClass: 'ui-tabs-nav',
516
+ panelClass: 'ui-tabs-panel',
517
+ panelTemplate: '<div></div>',
518
+ selectedClass: 'ui-tabs-selected',
519
+ spinner: 'Loading&#8230;',
520
+ tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
521
+ }
522
+ });
523
+
524
+ /*
525
+ * Tabs Extensions
526
+ */
527
+
528
+ /*
529
+ * Rotate
530
+ */
531
+ $.extend($.ui.tabs.prototype, {
532
+ rotation: null,
533
+ rotate: function(ms, continuing) {
534
+
535
+ continuing = continuing || false;
536
+
537
+ var self = this, t = this.options.selected;
538
+
539
+ function start() {
540
+ self.rotation = setInterval(function() {
541
+ t = ++t < self.$tabs.length ? t : 0;
542
+ self.select(t);
543
+ }, ms);
544
+ }
545
+
546
+ function stop(event) {
547
+ if (!event || event.clientX) { // only in case of a true click
548
+ clearInterval(self.rotation);
549
+ }
550
+ }
551
+
552
+ // start interval
553
+ if (ms) {
554
+ start();
555
+ if (!continuing)
556
+ this.$tabs.bind(this.options.event + '.tabs', stop);
557
+ else
558
+ this.$tabs.bind(this.options.event + '.tabs', function() {
559
+ stop();
560
+ t = self.options.selected;
561
+ start();
562
+ });
563
+ }
564
+ // stop interval
565
+ else {
566
+ stop();
567
+ this.$tabs.unbind(this.options.event + '.tabs', stop);
568
+ }
569
+ }
570
+ });
571
+
572
+ })(jQuery);