stuff_to_do_plugin 0.4.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 (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);