bumble 0.1.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 (125) hide show
  1. data/.gitignore +16 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +4 -0
  4. data/Rakefile +1 -0
  5. data/Readme.mdown +8 -0
  6. data/app/controllers/bumble_controller.rb +45 -0
  7. data/app/controllers/comments_controller.rb +99 -0
  8. data/app/controllers/password_resets_controller.rb +34 -0
  9. data/app/controllers/posts_controller.rb +103 -0
  10. data/app/controllers/user_sessions_controller.rb +22 -0
  11. data/app/controllers/users_controller.rb +60 -0
  12. data/app/helpers/bumble_helper.rb +50 -0
  13. data/app/models/asset.rb +57 -0
  14. data/app/models/comment.rb +78 -0
  15. data/app/models/notifier.rb +35 -0
  16. data/app/models/post.rb +57 -0
  17. data/app/models/posts/blog.rb +3 -0
  18. data/app/models/posts/code.rb +3 -0
  19. data/app/models/posts/image.rb +3 -0
  20. data/app/models/posts/link.rb +7 -0
  21. data/app/models/posts/quote.rb +3 -0
  22. data/app/models/posts/video.rb +4 -0
  23. data/app/models/user.rb +35 -0
  24. data/app/models/user_session.rb +2 -0
  25. data/app/views/comments/_comment.html.haml +14 -0
  26. data/app/views/comments/_form.html.haml +9 -0
  27. data/app/views/comments/edit.html.haml +7 -0
  28. data/app/views/comments/index.atom.builder +15 -0
  29. data/app/views/comments/new.html.haml +7 -0
  30. data/app/views/layouts/_sidebar.html.haml +20 -0
  31. data/app/views/layouts/bumble.html.haml +35 -0
  32. data/app/views/notifier/activation_confirmation.erb +5 -0
  33. data/app/views/notifier/activation_instructions.erb +5 -0
  34. data/app/views/notifier/new_comment_alert.erb +8 -0
  35. data/app/views/notifier/password_reset_instructions.erb +8 -0
  36. data/app/views/password_resets/edit.html.haml +10 -0
  37. data/app/views/password_resets/new.html.haml +7 -0
  38. data/app/views/posts/_form.html.haml +10 -0
  39. data/app/views/posts/_post.html.haml +16 -0
  40. data/app/views/posts/_preview.html.haml +8 -0
  41. data/app/views/posts/edit.html.haml +10 -0
  42. data/app/views/posts/forms/_blog.html.haml +4 -0
  43. data/app/views/posts/forms/_code.html.haml +6 -0
  44. data/app/views/posts/forms/_image.html.haml +9 -0
  45. data/app/views/posts/forms/_link.html.haml +6 -0
  46. data/app/views/posts/forms/_quote.html.haml +4 -0
  47. data/app/views/posts/forms/_video.html.haml +6 -0
  48. data/app/views/posts/index.atom.builder +15 -0
  49. data/app/views/posts/index.html.haml +21 -0
  50. data/app/views/posts/new.html.haml +8 -0
  51. data/app/views/posts/search.html.haml +27 -0
  52. data/app/views/posts/show.html.haml +22 -0
  53. data/app/views/posts/types/_blog.html.haml +4 -0
  54. data/app/views/posts/types/_code.html.haml +7 -0
  55. data/app/views/posts/types/_image.html.haml +3 -0
  56. data/app/views/posts/types/_link.html.haml +3 -0
  57. data/app/views/posts/types/_quote.html.haml +2 -0
  58. data/app/views/posts/types/_video.html.haml +5 -0
  59. data/app/views/user_sessions/new.html.haml +10 -0
  60. data/app/views/users/_form.html.haml +12 -0
  61. data/app/views/users/_user.html.haml +3 -0
  62. data/app/views/users/delete.html.haml +5 -0
  63. data/app/views/users/edit.html.haml +8 -0
  64. data/app/views/users/index.html.haml +3 -0
  65. data/app/views/users/new.html.haml +7 -0
  66. data/app/views/users/show.html.haml +6 -0
  67. data/bumble.gemspec +31 -0
  68. data/config/initializers/bumble.rb +9 -0
  69. data/config/initializers/haml.rb +2 -0
  70. data/config/initializers/html5_forms.rb +163 -0
  71. data/config/initializers/inflections.rb +10 -0
  72. data/config/initializers/mime_types.rb +5 -0
  73. data/config/initializers/will_paginate.rb +3 -0
  74. data/config/routes.rb +29 -0
  75. data/lib/bumble.rb +18 -0
  76. data/lib/bumble/version.rb +3 -0
  77. data/lib/generators/bumble/assets_generator.rb +10 -0
  78. data/lib/generators/bumble/migrations_generator.rb +40 -0
  79. data/lib/generators/bumble/templates/assets/images/.gitignore +0 -0
  80. data/lib/generators/bumble/templates/assets/images/close.png +0 -0
  81. data/lib/generators/bumble/templates/assets/images/email.png +0 -0
  82. data/lib/generators/bumble/templates/assets/images/feed.png +0 -0
  83. data/lib/generators/bumble/templates/assets/images/image.png +0 -0
  84. data/lib/generators/bumble/templates/assets/images/loading.gif +0 -0
  85. data/lib/generators/bumble/templates/assets/images/preview.png +0 -0
  86. data/lib/generators/bumble/templates/assets/images/shortcut.png +0 -0
  87. data/lib/generators/bumble/templates/assets/images/youtube.png +0 -0
  88. data/lib/generators/bumble/templates/assets/javascripts/admin.js +95 -0
  89. data/lib/generators/bumble/templates/assets/javascripts/bumble.js +76 -0
  90. data/lib/generators/bumble/templates/assets/javascripts/iphone.js +7 -0
  91. data/lib/generators/bumble/templates/assets/javascripts/jquery.autogrow.js +37 -0
  92. data/lib/generators/bumble/templates/assets/javascripts/jquery.cookie.js +96 -0
  93. data/lib/generators/bumble/templates/assets/javascripts/jquery.form.js +665 -0
  94. data/lib/generators/bumble/templates/assets/javascripts/jquery.hint.js +44 -0
  95. data/lib/generators/bumble/templates/assets/javascripts/jquery.jgrow.js +85 -0
  96. data/lib/generators/bumble/templates/assets/javascripts/jquery.js +154 -0
  97. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.core.js +18 -0
  98. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.tabs.js +13 -0
  99. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.widget.js +18 -0
  100. data/lib/generators/bumble/templates/assets/javascripts/jquery.validate.js +16 -0
  101. data/lib/generators/bumble/templates/assets/javascripts/modernizr.js +13 -0
  102. data/lib/generators/bumble/templates/assets/javascripts/ui.core.js +289 -0
  103. data/lib/generators/bumble/templates/assets/javascripts/ui.tabs.js +593 -0
  104. data/lib/generators/bumble/templates/migrations/add_approved_to_comments.rb +15 -0
  105. data/lib/generators/bumble/templates/migrations/add_delta_to_posts.rb +9 -0
  106. data/lib/generators/bumble/templates/migrations/add_missing_indexes.rb +24 -0
  107. data/lib/generators/bumble/templates/migrations/add_url_to_users.rb +9 -0
  108. data/lib/generators/bumble/templates/migrations/create_assets.rb +18 -0
  109. data/lib/generators/bumble/templates/migrations/create_comments.rb +19 -0
  110. data/lib/generators/bumble/templates/migrations/create_posts.rb +25 -0
  111. data/lib/generators/bumble/templates/migrations/create_users.rb +28 -0
  112. data/lib/generators/bumble/templates/migrations/full_text_search.rb +13 -0
  113. data/public/stylesheets/sass/bumble.sass +423 -0
  114. data/public/stylesheets/sass/iphone.sass +79 -0
  115. data/public/stylesheets/sass/print.sass +2 -0
  116. data/public/stylesheets/sass/reset.sass +49 -0
  117. data/test/factories.rb +28 -0
  118. data/test/functional/comments_controller_test.rb +177 -0
  119. data/test/functional/password_resets_controller_test.rb +61 -0
  120. data/test/functional/posts_controller_test.rb +181 -0
  121. data/test/test_helper.rb +113 -0
  122. data/test/unit/comment_test.rb +18 -0
  123. data/test/unit/post_test.rb +18 -0
  124. data/test/unit/user_test.rb +37 -0
  125. metadata +403 -0
@@ -0,0 +1,593 @@
1
+ /*
2
+ * jQuery UI Tabs
3
+ *
4
+ * Copyright (c) 2007, 2008 Klaus Hartl (stilbuero.de)
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
+ */
14
+ (function($) {
15
+
16
+ $.widget("ui.tabs", {
17
+ init: function() {
18
+ this.options.event += '.tabs'; // namespace event
19
+
20
+ // create tabs
21
+ this.tabify(true);
22
+ },
23
+ setData: function(key, value) {
24
+ if ((/^selected/).test(key))
25
+ this.select(value);
26
+ else {
27
+ this.options[key] = value;
28
+ this.tabify();
29
+ }
30
+ },
31
+ length: function() {
32
+ return this.$tabs.length;
33
+ },
34
+ tabId: function(a) {
35
+ return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '')
36
+ || this.options.idPrefix + $.data(a);
37
+ },
38
+ ui: function(tab, panel) {
39
+ return {
40
+ options: this.options,
41
+ tab: tab,
42
+ panel: panel
43
+ };
44
+ },
45
+ tabify: function(init) {
46
+
47
+ this.$lis = $('li:has(a[href])', this.element);
48
+ this.$tabs = this.$lis.map(function() { return $('a', this)[0]; });
49
+ this.$panels = $([]);
50
+
51
+ var self = this, o = this.options;
52
+
53
+ this.$tabs.each(function(i, a) {
54
+ // inline tab
55
+ if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash
56
+ self.$panels = self.$panels.add(a.hash);
57
+ // remote tab
58
+ else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#"
59
+ $.data(a, 'href.tabs', a.href); // required for restore on destroy
60
+ $.data(a, 'load.tabs', a.href); // mutable
61
+ var id = self.tabId(a);
62
+ a.href = '#' + id;
63
+ var $panel = $('#' + id);
64
+ if (!$panel.length) {
65
+ $panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass)
66
+ .insertAfter( self.$panels[i - 1] || self.element );
67
+ $panel.data('destroy.tabs', true);
68
+ }
69
+ self.$panels = self.$panels.add( $panel );
70
+ }
71
+ // invalid tab href
72
+ else
73
+ o.disabled.push(i + 1);
74
+ });
75
+
76
+ if (init) {
77
+
78
+ // attach necessary classes for styling if not present
79
+ this.element.addClass(o.navClass);
80
+ this.$panels.each(function() {
81
+ var $this = $(this);
82
+ $this.addClass(o.panelClass);
83
+ });
84
+
85
+ // Selected tab
86
+ // use "selected" option or try to retrieve:
87
+ // 1. from fragment identifier in url
88
+ // 2. from cookie
89
+ // 3. from selected class attribute on <li>
90
+ if (o.selected === undefined) {
91
+ if (location.hash) {
92
+ this.$tabs.each(function(i, a) {
93
+ if (a.hash == location.hash) {
94
+ o.selected = i;
95
+ // prevent page scroll to fragment
96
+ if ($.browser.msie || $.browser.opera) { // && !o.remote
97
+ var $toShow = $(location.hash), toShowId = $toShow.attr('id');
98
+ $toShow.attr('id', '');
99
+ setTimeout(function() {
100
+ $toShow.attr('id', toShowId); // restore id
101
+ }, 500);
102
+ }
103
+ scrollTo(0, 0);
104
+ return false; // break
105
+ }
106
+ });
107
+ }
108
+ else if (o.cookie) {
109
+ var index = parseInt($.cookie('ui-tabs' + $.data(self.element)),10);
110
+ if (index && self.$tabs[index])
111
+ o.selected = index;
112
+ }
113
+ else if (self.$lis.filter('.' + o.selectedClass).length)
114
+ o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] );
115
+ }
116
+ o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default
117
+
118
+ // Take disabling tabs via class attribute from HTML
119
+ // into account and update option properly.
120
+ // A selected tab cannot become disabled.
121
+ o.disabled = $.unique(o.disabled.concat(
122
+ $.map(this.$lis.filter('.' + o.disabledClass),
123
+ function(n, i) { return self.$lis.index(n); } )
124
+ )).sort();
125
+ if ($.inArray(o.selected, o.disabled) != -1)
126
+ o.disabled.splice($.inArray(o.selected, o.disabled), 1);
127
+
128
+ // highlight selected tab
129
+ this.$panels.addClass(o.hideClass);
130
+ this.$lis.removeClass(o.selectedClass);
131
+ if (o.selected !== null) {
132
+ this.$panels.eq(o.selected).show().removeClass(o.hideClass); // use show and remove class to show in any case no matter how it has been hidden before
133
+ this.$lis.eq(o.selected).addClass(o.selectedClass);
134
+
135
+ // seems to be expected behavior that the show callback is fired
136
+ var onShow = function() {
137
+ $(self.element).triggerHandler('tabsshow',
138
+ [self.ui(self.$tabs[o.selected], self.$panels[o.selected])], o.show);
139
+ };
140
+
141
+ // load if remote tab
142
+ if ($.data(this.$tabs[o.selected], 'load.tabs'))
143
+ this.load(o.selected, onShow);
144
+ // just trigger show event
145
+ else
146
+ onShow();
147
+
148
+ }
149
+
150
+ // clean up to avoid memory leaks in certain versions of IE 6
151
+ $(window).bind('unload', function() {
152
+ self.$tabs.unbind('.tabs');
153
+ self.$lis = self.$tabs = self.$panels = null;
154
+ });
155
+
156
+ }
157
+
158
+ // disable tabs
159
+ for (var i = 0, li; li = this.$lis[i]; i++)
160
+ $(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass);
161
+
162
+ // reset cache if switching from cached to not cached
163
+ if (o.cache === false)
164
+ this.$tabs.removeData('cache.tabs');
165
+
166
+ // set up animations
167
+ var hideFx, showFx, baseFx = { 'min-width': 0, duration: 1 }, baseDuration = 'normal';
168
+ if (o.fx && o.fx.constructor == Array)
169
+ hideFx = o.fx[0] || baseFx, showFx = o.fx[1] || baseFx;
170
+ else
171
+ hideFx = showFx = o.fx || baseFx;
172
+
173
+ // reset some styles to maintain print style sheets etc.
174
+ var resetCSS = { display: '', overflow: '', height: '' };
175
+ if (!$.browser.msie) // not in IE to prevent ClearType font issue
176
+ resetCSS.opacity = '';
177
+
178
+ // Hide a tab, animation prevents browser scrolling to fragment,
179
+ // $show is optional.
180
+ function hideTab(clicked, $hide, $show) {
181
+ $hide.animate(hideFx, hideFx.duration || baseDuration, function() { //
182
+ $hide.addClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
183
+ if ($.browser.msie && hideFx.opacity)
184
+ $hide[0].style.filter = '';
185
+ if ($show)
186
+ showTab(clicked, $show, $hide);
187
+ });
188
+ }
189
+
190
+ // Show a tab, animation prevents browser scrolling to fragment,
191
+ // $hide is optional.
192
+ function showTab(clicked, $show, $hide) {
193
+ if (showFx === baseFx)
194
+ $show.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab panels
195
+ $show.animate(showFx, showFx.duration || baseDuration, function() {
196
+ $show.removeClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
197
+ if ($.browser.msie && showFx.opacity)
198
+ $show[0].style.filter = '';
199
+
200
+ // callback
201
+ $(self.element).triggerHandler('tabsshow',
202
+ [self.ui(clicked, $show[0])], o.show);
203
+
204
+ });
205
+ }
206
+
207
+ // switch a tab
208
+ function switchTab(clicked, $li, $hide, $show) {
209
+ /*if (o.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click
210
+ $.ajaxHistory.update(clicked.hash);
211
+ }*/
212
+ $li.addClass(o.selectedClass)
213
+ .siblings().removeClass(o.selectedClass);
214
+ hideTab(clicked, $hide, $show);
215
+ }
216
+
217
+ // attach tab event handler, unbind to avoid duplicates from former tabifying...
218
+ this.$tabs.unbind('.tabs').bind(o.event, function() {
219
+
220
+ //var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
221
+ var $li = $(this).parents('li:eq(0)'),
222
+ $hide = self.$panels.filter(':visible'),
223
+ $show = $(this.hash);
224
+
225
+ // If tab is already selected and not unselectable or tab disabled or
226
+ // or is already loading or click callback returns false stop here.
227
+ // Check if click handler returns false last so that it is not executed
228
+ // for a disabled or loading tab!
229
+ if (($li.hasClass(o.selectedClass) && !o.unselect)
230
+ || $li.hasClass(o.disabledClass)
231
+ || $(this).hasClass(o.loadingClass)
232
+ || $(self.element).triggerHandler('tabsselect', [self.ui(this, $show[0])], o.select) === false
233
+ ) {
234
+ this.blur();
235
+ return false;
236
+ }
237
+
238
+ self.options.selected = self.$tabs.index(this);
239
+
240
+ // if tab may be closed
241
+ if (o.unselect) {
242
+ if ($li.hasClass(o.selectedClass)) {
243
+ self.options.selected = null;
244
+ $li.removeClass(o.selectedClass);
245
+ self.$panels.stop();
246
+ hideTab(this, $hide);
247
+ this.blur();
248
+ return false;
249
+ } else if (!$hide.length) {
250
+ self.$panels.stop();
251
+ var a = this;
252
+ self.load(self.$tabs.index(this), function() {
253
+ $li.addClass(o.selectedClass).addClass(o.unselectClass);
254
+ showTab(a, $show);
255
+ });
256
+ this.blur();
257
+ return false;
258
+ }
259
+ }
260
+
261
+ if (o.cookie)
262
+ $.cookie('ui-tabs' + $.data(self.element), self.options.selected, o.cookie);
263
+
264
+ // stop possibly running animations
265
+ self.$panels.stop();
266
+
267
+ // show new tab
268
+ if ($show.length) {
269
+
270
+ // prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled
271
+ /*if ($.browser.msie && o.bookmarkable) {
272
+ var showId = this.hash.replace('#', '');
273
+ $show.attr('id', '');
274
+ setTimeout(function() {
275
+ $show.attr('id', showId); // restore id
276
+ }, 0);
277
+ }*/
278
+
279
+ var a = this;
280
+ self.load(self.$tabs.index(this), $hide.length ?
281
+ function() {
282
+ switchTab(a, $li, $hide, $show);
283
+ } :
284
+ function() {
285
+ $li.addClass(o.selectedClass);
286
+ showTab(a, $show);
287
+ }
288
+ );
289
+
290
+ // Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash
291
+ /*var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0;
292
+ var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0;
293
+ setTimeout(function() {
294
+ scrollTo(scrollX, scrollY);
295
+ }, 0);*/
296
+
297
+ } else
298
+ throw 'jQuery UI Tabs: Mismatching fragment identifier.';
299
+
300
+ // Prevent IE from keeping other link focussed when using the back button
301
+ // and remove dotted border from clicked link. This is controlled in modern
302
+ // browsers via CSS, also blur removes focus from address bar in Firefox
303
+ // which can become a usability and annoying problem with tabsRotate.
304
+ if ($.browser.msie)
305
+ this.blur();
306
+
307
+ //return o.bookmarkable && !!trueClick; // convert trueClick == undefined to Boolean required in IE
308
+ return false;
309
+
310
+ });
311
+
312
+ // disable click if event is configured to something else
313
+ if (!(/^click/).test(o.event))
314
+ this.$tabs.bind('click.tabs', function() { return false; });
315
+
316
+ },
317
+ add: function(url, label, index) {
318
+ if (index == undefined)
319
+ index = this.$tabs.length; // append by default
320
+
321
+ var o = this.options;
322
+ var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label));
323
+ $li.data('destroy.tabs', true);
324
+
325
+ var id = url.indexOf('#') == 0 ? url.replace('#', '') : this.tabId( $('a:first-child', $li)[0] );
326
+
327
+ // try to find an existing element before creating a new one
328
+ var $panel = $('#' + id);
329
+ if (!$panel.length) {
330
+ $panel = $(o.panelTemplate).attr('id', id)
331
+ .addClass(o.hideClass)
332
+ .data('destroy.tabs', true);
333
+ }
334
+ $panel.addClass(o.panelClass);
335
+ if (index >= this.$lis.length) {
336
+ $li.appendTo(this.element);
337
+ $panel.appendTo(this.element[0].parentNode);
338
+ } else {
339
+ $li.insertBefore(this.$lis[index]);
340
+ $panel.insertBefore(this.$panels[index]);
341
+ }
342
+
343
+ o.disabled = $.map(o.disabled,
344
+ function(n, i) { return n >= index ? ++n : n });
345
+
346
+ this.tabify();
347
+
348
+ if (this.$tabs.length == 1) {
349
+ $li.addClass(o.selectedClass);
350
+ $panel.removeClass(o.hideClass);
351
+ var href = $.data(this.$tabs[0], 'load.tabs');
352
+ if (href)
353
+ this.load(index, href);
354
+ }
355
+
356
+ // callback
357
+ this.element.triggerHandler('tabsadd',
358
+ [this.ui(this.$tabs[index], this.$panels[index])], o.add
359
+ );
360
+ },
361
+ remove: function(index) {
362
+ var o = this.options, $li = this.$lis.eq(index).remove(),
363
+ $panel = this.$panels.eq(index).remove();
364
+
365
+ // If selected tab was removed focus tab to the right or
366
+ // in case the last tab was removed the tab to the left.
367
+ if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1)
368
+ this.select(index + (index + 1 < this.$tabs.length ? 1 : -1));
369
+
370
+ o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
371
+ function(n, i) { return n >= index ? --n : n });
372
+
373
+ this.tabify();
374
+
375
+ // callback
376
+ this.element.triggerHandler('tabsremove',
377
+ [this.ui($li.find('a')[0], $panel[0])], o.remove
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.element.triggerHandler('tabsenable',
397
+ [this.ui(this.$tabs[index], this.$panels[index])], o.enable
398
+ );
399
+
400
+ },
401
+ disable: function(index) {
402
+ var self = this, o = this.options;
403
+ if (index != o.selected) { // cannot disable already selected tab
404
+ this.$lis.eq(index).addClass(o.disabledClass);
405
+
406
+ o.disabled.push(index);
407
+ o.disabled.sort();
408
+
409
+ // callback
410
+ this.element.triggerHandler('tabsdisable',
411
+ [this.ui(this.$tabs[index], this.$panels[index])], o.disable
412
+ );
413
+ }
414
+ },
415
+ select: function(index) {
416
+ if (typeof index == 'string')
417
+ index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] );
418
+ this.$tabs.eq(index).trigger(this.options.event);
419
+ },
420
+ load: function(index, callback) { // callback is for internal usage only
421
+
422
+ var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0],
423
+ bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs');
424
+
425
+ callback = callback || function() {};
426
+
427
+ // no remote or from cache - just finish with callback
428
+ if (!url || !bypassCache && $.data(a, 'cache.tabs')) {
429
+ callback();
430
+ return;
431
+ }
432
+
433
+ // load remote from here on
434
+
435
+ var inner = function(parent) {
436
+ var $parent = $(parent), $inner = $parent.find('*:last');
437
+ return $inner.length && $inner || $parent;
438
+ };
439
+ var cleanup = function() {
440
+ self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass)
441
+ .each(function() {
442
+ if (o.spinner)
443
+ inner(this).parent().html(inner(this).data('label.tabs'));
444
+ });
445
+ self.xhr = null;
446
+ };
447
+
448
+ if (o.spinner) {
449
+ var label = inner(a).html();
450
+ inner(a).wrapInner('<em></em>')
451
+ .find('em').data('label.tabs', label).html(o.spinner);
452
+ }
453
+
454
+ var ajaxOptions = $.extend({}, o.ajaxOptions, {
455
+ url: url,
456
+ success: function(r, s) {
457
+ $(a.hash).html(r);
458
+ cleanup();
459
+
460
+ if (o.cache)
461
+ $.data(a, 'cache.tabs', true); // if loaded once do not load them again
462
+
463
+ // callbacks
464
+ $(self.element).triggerHandler('tabsload',
465
+ [self.ui(self.$tabs[index], self.$panels[index])], o.load
466
+ );
467
+ o.ajaxOptions.success && o.ajaxOptions.success(r, s);
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
+ setTimeout(function() { // timeout is again required in IE, "wait" for id being restored
482
+ self.xhr = $.ajax(ajaxOptions);
483
+ }, 0);
484
+
485
+ },
486
+ url: function(index, url) {
487
+ this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url);
488
+ },
489
+ destroy: function() {
490
+ var o = this.options;
491
+ this.element.unbind('.tabs')
492
+ .removeClass(o.navClass).removeData('tabs');
493
+ this.$tabs.each(function() {
494
+ var href = $.data(this, 'href.tabs');
495
+ if (href)
496
+ this.href = href;
497
+ var $this = $(this).unbind('.tabs');
498
+ $.each(['href', 'load', 'cache'], function(i, prefix) {
499
+ $this.removeData(prefix + '.tabs');
500
+ });
501
+ });
502
+ this.$lis.add(this.$panels).each(function() {
503
+ if ($.data(this, 'destroy.tabs'))
504
+ $(this).remove();
505
+ else
506
+ $(this).removeClass([o.selectedClass, o.unselectClass,
507
+ o.disabledClass, o.panelClass, o.hideClass].join(' '));
508
+ });
509
+ }
510
+ });
511
+
512
+ $.ui.tabs.defaults = {
513
+ // basic setup
514
+ unselect: false,
515
+ event: 'click',
516
+ disabled: [],
517
+ cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
518
+ // TODO history: false,
519
+
520
+ // Ajax
521
+ spinner: 'Loading&#8230;',
522
+ cache: false,
523
+ idPrefix: 'ui-tabs-',
524
+ ajaxOptions: {},
525
+
526
+ // animations
527
+ fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
528
+
529
+ // templates
530
+ tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>',
531
+ panelTemplate: '<div></div>',
532
+
533
+ // CSS classes
534
+ navClass: 'ui-tabs-nav',
535
+ selectedClass: 'ui-tabs-selected',
536
+ unselectClass: 'ui-tabs-unselect',
537
+ disabledClass: 'ui-tabs-disabled',
538
+ panelClass: 'ui-tabs-panel',
539
+ hideClass: 'ui-tabs-hide',
540
+ loadingClass: 'ui-tabs-loading'
541
+ };
542
+
543
+ $.ui.tabs.getter = "length";
544
+
545
+ /*
546
+ * Tabs Extensions
547
+ */
548
+
549
+ /*
550
+ * Rotate
551
+ */
552
+ $.extend($.ui.tabs.prototype, {
553
+ rotation: null,
554
+ rotate: function(ms, continuing) {
555
+
556
+ continuing = continuing || false;
557
+
558
+ var self = this, t = this.options.selected;
559
+
560
+ function start() {
561
+ self.rotation = setInterval(function() {
562
+ t = ++t < self.$tabs.length ? t : 0;
563
+ self.select(t);
564
+ }, ms);
565
+ }
566
+
567
+ function stop(e) {
568
+ if (!e || e.clientX) { // only in case of a true click
569
+ clearInterval(self.rotation);
570
+ }
571
+ }
572
+
573
+ // start interval
574
+ if (ms) {
575
+ start();
576
+ if (!continuing)
577
+ this.$tabs.bind(this.options.event, stop);
578
+ else
579
+ this.$tabs.bind(this.options.event, function() {
580
+ stop();
581
+ t = self.options.selected;
582
+ start();
583
+ });
584
+ }
585
+ // stop interval
586
+ else {
587
+ stop();
588
+ this.$tabs.unbind(this.options.event, stop);
589
+ }
590
+ }
591
+ });
592
+
593
+ })(jQuery);