tarantula-rails3 0.3.3
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.
- data/CHANGELOG +49 -0
- data/LICENSE +20 -0
- data/README.rdoc +161 -0
- data/Rakefile +83 -0
- data/VERSION.yml +4 -0
- data/examples/example_helper.rb +57 -0
- data/examples/relevance/core_extensions/ellipsize_example.rb +19 -0
- data/examples/relevance/core_extensions/file_example.rb +8 -0
- data/examples/relevance/core_extensions/response_example.rb +29 -0
- data/examples/relevance/core_extensions/test_case_example.rb +20 -0
- data/examples/relevance/tarantula/attack_handler_example.rb +29 -0
- data/examples/relevance/tarantula/basic_attack_example.rb +12 -0
- data/examples/relevance/tarantula/crawler_example.rb +375 -0
- data/examples/relevance/tarantula/form_example.rb +50 -0
- data/examples/relevance/tarantula/form_submission_example.rb +171 -0
- data/examples/relevance/tarantula/html_document_handler_example.rb +43 -0
- data/examples/relevance/tarantula/html_report_helper_example.rb +46 -0
- data/examples/relevance/tarantula/html_reporter_example.rb +82 -0
- data/examples/relevance/tarantula/invalid_html_handler_example.rb +33 -0
- data/examples/relevance/tarantula/io_reporter_example.rb +11 -0
- data/examples/relevance/tarantula/link_example.rb +84 -0
- data/examples/relevance/tarantula/log_grabber_example.rb +26 -0
- data/examples/relevance/tarantula/rails_integration_proxy_example.rb +88 -0
- data/examples/relevance/tarantula/result_example.rb +85 -0
- data/examples/relevance/tarantula/tidy_handler_example.rb +58 -0
- data/examples/relevance/tarantula/transform_example.rb +20 -0
- data/examples/relevance/tarantula_example.rb +23 -0
- data/laf/images/header_bg.jpg +0 -0
- data/laf/images/logo.png +0 -0
- data/laf/images/tagline.png +0 -0
- data/laf/javascripts/jquery-1.2.3.js +3408 -0
- data/laf/javascripts/jquery-ui-tabs.js +890 -0
- data/laf/javascripts/jquery.tablesorter.js +861 -0
- data/laf/javascripts/tarantula.js +10 -0
- data/laf/stylesheets/tarantula.css +346 -0
- data/lib/relevance/core_extensions/ellipsize.rb +34 -0
- data/lib/relevance/core_extensions/file.rb +9 -0
- data/lib/relevance/core_extensions/metaclass.rb +78 -0
- data/lib/relevance/core_extensions/response.rb +9 -0
- data/lib/relevance/core_extensions/string_chars_fix.rb +11 -0
- data/lib/relevance/core_extensions/test_case.rb +19 -0
- data/lib/relevance/tarantula.rb +58 -0
- data/lib/relevance/tarantula/attack.rb +18 -0
- data/lib/relevance/tarantula/attack_handler.rb +37 -0
- data/lib/relevance/tarantula/basic_attack.rb +40 -0
- data/lib/relevance/tarantula/crawler.rb +254 -0
- data/lib/relevance/tarantula/detail.html.erb +81 -0
- data/lib/relevance/tarantula/form.rb +23 -0
- data/lib/relevance/tarantula/form_submission.rb +88 -0
- data/lib/relevance/tarantula/html_document_handler.rb +36 -0
- data/lib/relevance/tarantula/html_report_helper.rb +39 -0
- data/lib/relevance/tarantula/html_reporter.rb +105 -0
- data/lib/relevance/tarantula/index.html.erb +37 -0
- data/lib/relevance/tarantula/invalid_html_handler.rb +18 -0
- data/lib/relevance/tarantula/io_reporter.rb +34 -0
- data/lib/relevance/tarantula/link.rb +94 -0
- data/lib/relevance/tarantula/log_grabber.rb +16 -0
- data/lib/relevance/tarantula/rails_integration_proxy.rb +68 -0
- data/lib/relevance/tarantula/recording.rb +12 -0
- data/lib/relevance/tarantula/response.rb +13 -0
- data/lib/relevance/tarantula/result.rb +77 -0
- data/lib/relevance/tarantula/test_report.html.erb +32 -0
- data/lib/relevance/tarantula/tidy_handler.rb +32 -0
- data/lib/relevance/tarantula/transform.rb +17 -0
- data/lib/relevance/tasks/tarantula_tasks.rake +42 -0
- data/lib/tarantula-rails3.rb +9 -0
- data/template/tarantula_test.rb +22 -0
- metadata +164 -0
@@ -0,0 +1,890 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery UI @VERSION
|
3
|
+
*
|
4
|
+
* Copyright (c) 2008 Paul Bakaus (ui.jquery.com)
|
5
|
+
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
6
|
+
* and GPL (GPL-LICENSE.txt) licenses.
|
7
|
+
*
|
8
|
+
* http://docs.jquery.com/UI
|
9
|
+
*/
|
10
|
+
;(function($) {
|
11
|
+
|
12
|
+
$.ui = {
|
13
|
+
plugin: {
|
14
|
+
add: function(module, option, set) {
|
15
|
+
var proto = $.ui[module].prototype;
|
16
|
+
for(var i in set) {
|
17
|
+
proto.plugins[i] = proto.plugins[i] || [];
|
18
|
+
proto.plugins[i].push([option, set[i]]);
|
19
|
+
}
|
20
|
+
},
|
21
|
+
call: function(instance, name, args) {
|
22
|
+
var set = instance.plugins[name];
|
23
|
+
if(!set) { return; }
|
24
|
+
|
25
|
+
for (var i = 0; i < set.length; i++) {
|
26
|
+
if (instance.options[set[i][0]]) {
|
27
|
+
set[i][1].apply(instance.element, args);
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
},
|
32
|
+
cssCache: {},
|
33
|
+
css: function(name) {
|
34
|
+
if ($.ui.cssCache[name]) { return $.ui.cssCache[name]; }
|
35
|
+
var tmp = $('<div class="ui-gen">').addClass(name).css({position:'absolute', top:'-5000px', left:'-5000px', display:'block'}).appendTo('body');
|
36
|
+
|
37
|
+
//if (!$.browser.safari)
|
38
|
+
//tmp.appendTo('body');
|
39
|
+
|
40
|
+
//Opera and Safari set width and height to 0px instead of auto
|
41
|
+
//Safari returns rgba(0,0,0,0) when bgcolor is not set
|
42
|
+
$.ui.cssCache[name] = !!(
|
43
|
+
(!(/auto|default/).test(tmp.css('cursor')) || (/^[1-9]/).test(tmp.css('height')) || (/^[1-9]/).test(tmp.css('width')) ||
|
44
|
+
!(/none/).test(tmp.css('backgroundImage')) || !(/transparent|rgba\(0, 0, 0, 0\)/).test(tmp.css('backgroundColor')))
|
45
|
+
);
|
46
|
+
try { $('body').get(0).removeChild(tmp.get(0)); } catch(e){}
|
47
|
+
return $.ui.cssCache[name];
|
48
|
+
},
|
49
|
+
disableSelection: function(el) {
|
50
|
+
$(el).attr('unselectable', 'on').css('MozUserSelect', 'none');
|
51
|
+
},
|
52
|
+
enableSelection: function(el) {
|
53
|
+
$(el).attr('unselectable', 'off').css('MozUserSelect', '');
|
54
|
+
},
|
55
|
+
hasScroll: function(e, a) {
|
56
|
+
var scroll = /top/.test(a||"top") ? 'scrollTop' : 'scrollLeft', has = false;
|
57
|
+
if (e[scroll] > 0) return true; e[scroll] = 1;
|
58
|
+
has = e[scroll] > 0 ? true : false; e[scroll] = 0;
|
59
|
+
return has;
|
60
|
+
}
|
61
|
+
};
|
62
|
+
|
63
|
+
|
64
|
+
/** jQuery core modifications and additions **/
|
65
|
+
|
66
|
+
var _remove = $.fn.remove;
|
67
|
+
$.fn.remove = function() {
|
68
|
+
$("*", this).add(this).triggerHandler("remove");
|
69
|
+
return _remove.apply(this, arguments );
|
70
|
+
};
|
71
|
+
|
72
|
+
// $.widget is a factory to create jQuery plugins
|
73
|
+
// taking some boilerplate code out of the plugin code
|
74
|
+
// created by Scott González and Jörn Zaefferer
|
75
|
+
function getter(namespace, plugin, method) {
|
76
|
+
var methods = $[namespace][plugin].getter || [];
|
77
|
+
methods = (typeof methods == "string" ? methods.split(/,?\s+/) : methods);
|
78
|
+
return ($.inArray(method, methods) != -1);
|
79
|
+
}
|
80
|
+
|
81
|
+
$.widget = function(name, prototype) {
|
82
|
+
var namespace = name.split(".")[0];
|
83
|
+
name = name.split(".")[1];
|
84
|
+
|
85
|
+
// create plugin method
|
86
|
+
$.fn[name] = function(options) {
|
87
|
+
var isMethodCall = (typeof options == 'string'),
|
88
|
+
args = Array.prototype.slice.call(arguments, 1);
|
89
|
+
|
90
|
+
if (isMethodCall && getter(namespace, name, options)) {
|
91
|
+
var instance = $.data(this[0], name);
|
92
|
+
return (instance ? instance[options].apply(instance, args)
|
93
|
+
: undefined);
|
94
|
+
}
|
95
|
+
|
96
|
+
return this.each(function() {
|
97
|
+
var instance = $.data(this, name);
|
98
|
+
if (isMethodCall && instance && $.isFunction(instance[options])) {
|
99
|
+
instance[options].apply(instance, args);
|
100
|
+
} else if (!isMethodCall) {
|
101
|
+
$.data(this, name, new $[namespace][name](this, options));
|
102
|
+
}
|
103
|
+
});
|
104
|
+
};
|
105
|
+
|
106
|
+
// create widget constructor
|
107
|
+
$[namespace][name] = function(element, options) {
|
108
|
+
var self = this;
|
109
|
+
|
110
|
+
this.widgetName = name;
|
111
|
+
this.widgetBaseClass = namespace + '-' + name;
|
112
|
+
|
113
|
+
this.options = $.extend({}, $.widget.defaults, $[namespace][name].defaults, options);
|
114
|
+
this.element = $(element)
|
115
|
+
.bind('setData.' + name, function(e, key, value) {
|
116
|
+
return self.setData(key, value);
|
117
|
+
})
|
118
|
+
.bind('getData.' + name, function(e, key) {
|
119
|
+
return self.getData(key);
|
120
|
+
})
|
121
|
+
.bind('remove', function() {
|
122
|
+
return self.destroy();
|
123
|
+
});
|
124
|
+
this.init();
|
125
|
+
};
|
126
|
+
|
127
|
+
// add widget prototype
|
128
|
+
$[namespace][name].prototype = $.extend({}, $.widget.prototype, prototype);
|
129
|
+
};
|
130
|
+
|
131
|
+
$.widget.prototype = {
|
132
|
+
init: function() {},
|
133
|
+
destroy: function() {
|
134
|
+
this.element.removeData(this.widgetName);
|
135
|
+
},
|
136
|
+
|
137
|
+
getData: function(key) {
|
138
|
+
return this.options[key];
|
139
|
+
},
|
140
|
+
setData: function(key, value) {
|
141
|
+
this.options[key] = value;
|
142
|
+
|
143
|
+
if (key == 'disabled') {
|
144
|
+
this.element[value ? 'addClass' : 'removeClass'](
|
145
|
+
this.widgetBaseClass + '-disabled');
|
146
|
+
}
|
147
|
+
},
|
148
|
+
|
149
|
+
enable: function() {
|
150
|
+
this.setData('disabled', false);
|
151
|
+
},
|
152
|
+
disable: function() {
|
153
|
+
this.setData('disabled', true);
|
154
|
+
}
|
155
|
+
};
|
156
|
+
|
157
|
+
$.widget.defaults = {
|
158
|
+
disabled: false
|
159
|
+
};
|
160
|
+
|
161
|
+
|
162
|
+
/** Mouse Interaction Plugin **/
|
163
|
+
|
164
|
+
$.ui.mouse = {
|
165
|
+
mouseInit: function() {
|
166
|
+
var self = this;
|
167
|
+
|
168
|
+
this.element.bind('mousedown.'+this.widgetName, function(e) {
|
169
|
+
return self.mouseDown(e);
|
170
|
+
});
|
171
|
+
|
172
|
+
// Prevent text selection in IE
|
173
|
+
if ($.browser.msie) {
|
174
|
+
this._mouseUnselectable = this.element.attr('unselectable');
|
175
|
+
this.element.attr('unselectable', 'on');
|
176
|
+
}
|
177
|
+
|
178
|
+
this.started = false;
|
179
|
+
},
|
180
|
+
|
181
|
+
// TODO: make sure destroying one instance of mouse doesn't mess with
|
182
|
+
// other instances of mouse
|
183
|
+
mouseDestroy: function() {
|
184
|
+
this.element.unbind('.'+this.widgetName);
|
185
|
+
|
186
|
+
// Restore text selection in IE
|
187
|
+
($.browser.msie
|
188
|
+
&& this.element.attr('unselectable', this._mouseUnselectable));
|
189
|
+
},
|
190
|
+
|
191
|
+
mouseDown: function(e) {
|
192
|
+
// we may have missed mouseup (out of window)
|
193
|
+
(this._mouseStarted && this.mouseUp(e));
|
194
|
+
|
195
|
+
this._mouseDownEvent = e;
|
196
|
+
|
197
|
+
var self = this,
|
198
|
+
btnIsLeft = (e.which == 1),
|
199
|
+
elIsCancel = (typeof this.options.cancel == "string" ? $(e.target).parents().add(e.target).filter(this.options.cancel).length : false);
|
200
|
+
if (!btnIsLeft || elIsCancel || !this.mouseCapture(e)) {
|
201
|
+
return true;
|
202
|
+
}
|
203
|
+
|
204
|
+
this._mouseDelayMet = !this.options.delay;
|
205
|
+
if (!this._mouseDelayMet) {
|
206
|
+
this._mouseDelayTimer = setTimeout(function() {
|
207
|
+
self._mouseDelayMet = true;
|
208
|
+
}, this.options.delay);
|
209
|
+
}
|
210
|
+
|
211
|
+
if (this.mouseDistanceMet(e) && this.mouseDelayMet(e)) {
|
212
|
+
this._mouseStarted = (this.mouseStart(e) !== false);
|
213
|
+
if (!this._mouseStarted) {
|
214
|
+
e.preventDefault();
|
215
|
+
return true;
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
// these delegates are required to keep context
|
220
|
+
this._mouseMoveDelegate = function(e) {
|
221
|
+
return self.mouseMove(e);
|
222
|
+
};
|
223
|
+
this._mouseUpDelegate = function(e) {
|
224
|
+
return self.mouseUp(e);
|
225
|
+
};
|
226
|
+
$(document)
|
227
|
+
.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
|
228
|
+
.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
|
229
|
+
|
230
|
+
return false;
|
231
|
+
},
|
232
|
+
|
233
|
+
mouseMove: function(e) {
|
234
|
+
// IE mouseup check - mouseup happened when mouse was out of window
|
235
|
+
if ($.browser.msie && !e.button) {
|
236
|
+
return this.mouseUp(e);
|
237
|
+
}
|
238
|
+
|
239
|
+
if (this._mouseStarted) {
|
240
|
+
this.mouseDrag(e);
|
241
|
+
return false;
|
242
|
+
}
|
243
|
+
|
244
|
+
if (this.mouseDistanceMet(e) && this.mouseDelayMet(e)) {
|
245
|
+
this._mouseStarted =
|
246
|
+
(this.mouseStart(this._mouseDownEvent, e) !== false);
|
247
|
+
(this._mouseStarted ? this.mouseDrag(e) : this.mouseUp(e));
|
248
|
+
}
|
249
|
+
|
250
|
+
return !this._mouseStarted;
|
251
|
+
},
|
252
|
+
|
253
|
+
mouseUp: function(e) {
|
254
|
+
$(document)
|
255
|
+
.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
|
256
|
+
.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
|
257
|
+
|
258
|
+
if (this._mouseStarted) {
|
259
|
+
this._mouseStarted = false;
|
260
|
+
this.mouseStop(e);
|
261
|
+
}
|
262
|
+
|
263
|
+
return false;
|
264
|
+
},
|
265
|
+
|
266
|
+
mouseDistanceMet: function(e) {
|
267
|
+
return (Math.max(
|
268
|
+
Math.abs(this._mouseDownEvent.pageX - e.pageX),
|
269
|
+
Math.abs(this._mouseDownEvent.pageY - e.pageY)
|
270
|
+
) >= this.options.distance
|
271
|
+
);
|
272
|
+
},
|
273
|
+
|
274
|
+
mouseDelayMet: function(e) {
|
275
|
+
return this._mouseDelayMet;
|
276
|
+
},
|
277
|
+
|
278
|
+
// These are placeholder methods, to be overriden by extending plugin
|
279
|
+
mouseStart: function(e) {},
|
280
|
+
mouseDrag: function(e) {},
|
281
|
+
mouseStop: function(e) {},
|
282
|
+
mouseCapture: function(e) { return true; }
|
283
|
+
};
|
284
|
+
|
285
|
+
$.ui.mouse.defaults = {
|
286
|
+
cancel: null,
|
287
|
+
distance: 1,
|
288
|
+
delay: 0
|
289
|
+
};
|
290
|
+
|
291
|
+
})(jQuery);
|
292
|
+
/*
|
293
|
+
* jQuery UI Tabs
|
294
|
+
*
|
295
|
+
* Copyright (c) 2007, 2008 Klaus Hartl (stilbuero.de)
|
296
|
+
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
297
|
+
* and GPL (GPL-LICENSE.txt) licenses.
|
298
|
+
*
|
299
|
+
* http://docs.jquery.com/UI/Tabs
|
300
|
+
*
|
301
|
+
* Depends:
|
302
|
+
* ui.core.js
|
303
|
+
*/
|
304
|
+
(function($) {
|
305
|
+
|
306
|
+
$.widget("ui.tabs", {
|
307
|
+
init: function() {
|
308
|
+
this.options.event += '.tabs'; // namespace event
|
309
|
+
|
310
|
+
// create tabs
|
311
|
+
this.tabify(true);
|
312
|
+
},
|
313
|
+
setData: function(key, value) {
|
314
|
+
if ((/^selected/).test(key))
|
315
|
+
this.select(value);
|
316
|
+
else {
|
317
|
+
this.options[key] = value;
|
318
|
+
this.tabify();
|
319
|
+
}
|
320
|
+
},
|
321
|
+
length: function() {
|
322
|
+
return this.$tabs.length;
|
323
|
+
},
|
324
|
+
tabId: function(a) {
|
325
|
+
return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '')
|
326
|
+
|| this.options.idPrefix + $.data(a);
|
327
|
+
},
|
328
|
+
ui: function(tab, panel) {
|
329
|
+
return {
|
330
|
+
options: this.options,
|
331
|
+
tab: tab,
|
332
|
+
panel: panel,
|
333
|
+
index: this.$tabs.index(tab)
|
334
|
+
};
|
335
|
+
},
|
336
|
+
tabify: function(init) {
|
337
|
+
|
338
|
+
this.$lis = $('li:has(a[href])', this.element);
|
339
|
+
this.$tabs = this.$lis.map(function() { return $('a', this)[0]; });
|
340
|
+
this.$panels = $([]);
|
341
|
+
|
342
|
+
var self = this, o = this.options;
|
343
|
+
|
344
|
+
this.$tabs.each(function(i, a) {
|
345
|
+
// inline tab
|
346
|
+
if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash
|
347
|
+
self.$panels = self.$panels.add(a.hash);
|
348
|
+
// remote tab
|
349
|
+
else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#"
|
350
|
+
$.data(a, 'href.tabs', a.href); // required for restore on destroy
|
351
|
+
$.data(a, 'load.tabs', a.href); // mutable
|
352
|
+
var id = self.tabId(a);
|
353
|
+
a.href = '#' + id;
|
354
|
+
var $panel = $('#' + id);
|
355
|
+
if (!$panel.length) {
|
356
|
+
$panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass)
|
357
|
+
.insertAfter( self.$panels[i - 1] || self.element );
|
358
|
+
$panel.data('destroy.tabs', true);
|
359
|
+
}
|
360
|
+
self.$panels = self.$panels.add( $panel );
|
361
|
+
}
|
362
|
+
// invalid tab href
|
363
|
+
else
|
364
|
+
o.disabled.push(i + 1);
|
365
|
+
});
|
366
|
+
|
367
|
+
if (init) {
|
368
|
+
|
369
|
+
// attach necessary classes for styling if not present
|
370
|
+
this.element.addClass(o.navClass);
|
371
|
+
this.$panels.each(function() {
|
372
|
+
var $this = $(this);
|
373
|
+
$this.addClass(o.panelClass);
|
374
|
+
});
|
375
|
+
|
376
|
+
// Selected tab
|
377
|
+
// use "selected" option or try to retrieve:
|
378
|
+
// 1. from fragment identifier in url
|
379
|
+
// 2. from cookie
|
380
|
+
// 3. from selected class attribute on <li>
|
381
|
+
if (o.selected === undefined) {
|
382
|
+
if (location.hash) {
|
383
|
+
this.$tabs.each(function(i, a) {
|
384
|
+
if (a.hash == location.hash) {
|
385
|
+
o.selected = i;
|
386
|
+
// prevent page scroll to fragment
|
387
|
+
if ($.browser.msie || $.browser.opera) { // && !o.remote
|
388
|
+
var $toShow = $(location.hash), toShowId = $toShow.attr('id');
|
389
|
+
$toShow.attr('id', '');
|
390
|
+
setTimeout(function() {
|
391
|
+
$toShow.attr('id', toShowId); // restore id
|
392
|
+
}, 500);
|
393
|
+
}
|
394
|
+
scrollTo(0, 0);
|
395
|
+
return false; // break
|
396
|
+
}
|
397
|
+
});
|
398
|
+
}
|
399
|
+
else if (o.cookie) {
|
400
|
+
var index = parseInt($.cookie('ui-tabs' + $.data(self.element)),10);
|
401
|
+
if (index && self.$tabs[index])
|
402
|
+
o.selected = index;
|
403
|
+
}
|
404
|
+
else if (self.$lis.filter('.' + o.selectedClass).length)
|
405
|
+
o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] );
|
406
|
+
}
|
407
|
+
o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default
|
408
|
+
|
409
|
+
// Take disabling tabs via class attribute from HTML
|
410
|
+
// into account and update option properly.
|
411
|
+
// A selected tab cannot become disabled.
|
412
|
+
o.disabled = $.unique(o.disabled.concat(
|
413
|
+
$.map(this.$lis.filter('.' + o.disabledClass),
|
414
|
+
function(n, i) { return self.$lis.index(n); } )
|
415
|
+
)).sort();
|
416
|
+
if ($.inArray(o.selected, o.disabled) != -1)
|
417
|
+
o.disabled.splice($.inArray(o.selected, o.disabled), 1);
|
418
|
+
|
419
|
+
// highlight selected tab
|
420
|
+
this.$panels.addClass(o.hideClass);
|
421
|
+
this.$lis.removeClass(o.selectedClass);
|
422
|
+
if (o.selected !== null) {
|
423
|
+
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
|
424
|
+
this.$lis.eq(o.selected).addClass(o.selectedClass);
|
425
|
+
|
426
|
+
// seems to be expected behavior that the show callback is fired
|
427
|
+
var onShow = function() {
|
428
|
+
$(self.element).triggerHandler('tabsshow',
|
429
|
+
[self.fakeEvent('tabsshow'), self.ui(self.$tabs[o.selected], self.$panels[o.selected])], o.show);
|
430
|
+
};
|
431
|
+
|
432
|
+
// load if remote tab
|
433
|
+
if ($.data(this.$tabs[o.selected], 'load.tabs'))
|
434
|
+
this.load(o.selected, onShow);
|
435
|
+
// just trigger show event
|
436
|
+
else
|
437
|
+
onShow();
|
438
|
+
|
439
|
+
}
|
440
|
+
|
441
|
+
// clean up to avoid memory leaks in certain versions of IE 6
|
442
|
+
$(window).bind('unload', function() {
|
443
|
+
self.$tabs.unbind('.tabs');
|
444
|
+
self.$lis = self.$tabs = self.$panels = null;
|
445
|
+
});
|
446
|
+
|
447
|
+
}
|
448
|
+
|
449
|
+
// disable tabs
|
450
|
+
for (var i = 0, li; li = this.$lis[i]; i++)
|
451
|
+
$(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass);
|
452
|
+
|
453
|
+
// reset cache if switching from cached to not cached
|
454
|
+
if (o.cache === false)
|
455
|
+
this.$tabs.removeData('cache.tabs');
|
456
|
+
|
457
|
+
// set up animations
|
458
|
+
var hideFx, showFx, baseFx = { 'min-width': 0, duration: 1 }, baseDuration = 'normal';
|
459
|
+
if (o.fx && o.fx.constructor == Array)
|
460
|
+
hideFx = o.fx[0] || baseFx, showFx = o.fx[1] || baseFx;
|
461
|
+
else
|
462
|
+
hideFx = showFx = o.fx || baseFx;
|
463
|
+
|
464
|
+
// reset some styles to maintain print style sheets etc.
|
465
|
+
var resetCSS = { display: '', overflow: '', height: '' };
|
466
|
+
if (!$.browser.msie) // not in IE to prevent ClearType font issue
|
467
|
+
resetCSS.opacity = '';
|
468
|
+
|
469
|
+
// Hide a tab, animation prevents browser scrolling to fragment,
|
470
|
+
// $show is optional.
|
471
|
+
function hideTab(clicked, $hide, $show) {
|
472
|
+
$hide.animate(hideFx, hideFx.duration || baseDuration, function() { //
|
473
|
+
$hide.addClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
|
474
|
+
if ($.browser.msie && hideFx.opacity)
|
475
|
+
$hide[0].style.filter = '';
|
476
|
+
if ($show)
|
477
|
+
showTab(clicked, $show, $hide);
|
478
|
+
});
|
479
|
+
}
|
480
|
+
|
481
|
+
// Show a tab, animation prevents browser scrolling to fragment,
|
482
|
+
// $hide is optional.
|
483
|
+
function showTab(clicked, $show, $hide) {
|
484
|
+
if (showFx === baseFx)
|
485
|
+
$show.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab panels
|
486
|
+
$show.animate(showFx, showFx.duration || baseDuration, function() {
|
487
|
+
$show.removeClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
|
488
|
+
if ($.browser.msie && showFx.opacity)
|
489
|
+
$show[0].style.filter = '';
|
490
|
+
|
491
|
+
// callback
|
492
|
+
$(self.element).triggerHandler('tabsshow',
|
493
|
+
[self.fakeEvent('tabsshow'), self.ui(clicked, $show[0])], o.show);
|
494
|
+
|
495
|
+
});
|
496
|
+
}
|
497
|
+
|
498
|
+
// switch a tab
|
499
|
+
function switchTab(clicked, $li, $hide, $show) {
|
500
|
+
/*if (o.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click
|
501
|
+
$.ajaxHistory.update(clicked.hash);
|
502
|
+
}*/
|
503
|
+
$li.addClass(o.selectedClass)
|
504
|
+
.siblings().removeClass(o.selectedClass);
|
505
|
+
hideTab(clicked, $hide, $show);
|
506
|
+
}
|
507
|
+
|
508
|
+
// attach tab event handler, unbind to avoid duplicates from former tabifying...
|
509
|
+
this.$tabs.unbind('.tabs').bind(o.event, function() {
|
510
|
+
|
511
|
+
//var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
|
512
|
+
var $li = $(this).parents('li:eq(0)'),
|
513
|
+
$hide = self.$panels.filter(':visible'),
|
514
|
+
$show = $(this.hash);
|
515
|
+
|
516
|
+
// If tab is already selected and not unselectable or tab disabled or
|
517
|
+
// or is already loading or click callback returns false stop here.
|
518
|
+
// Check if click handler returns false last so that it is not executed
|
519
|
+
// for a disabled or loading tab!
|
520
|
+
if (($li.hasClass(o.selectedClass) && !o.unselect)
|
521
|
+
|| $li.hasClass(o.disabledClass)
|
522
|
+
|| $(this).hasClass(o.loadingClass)
|
523
|
+
|| $(self.element).triggerHandler('tabsselect', [self.fakeEvent('tabsselect'), self.ui(this, $show[0])], o.select) === false
|
524
|
+
) {
|
525
|
+
this.blur();
|
526
|
+
return false;
|
527
|
+
}
|
528
|
+
|
529
|
+
self.options.selected = self.$tabs.index(this);
|
530
|
+
|
531
|
+
// if tab may be closed
|
532
|
+
if (o.unselect) {
|
533
|
+
if ($li.hasClass(o.selectedClass)) {
|
534
|
+
self.options.selected = null;
|
535
|
+
$li.removeClass(o.selectedClass);
|
536
|
+
self.$panels.stop();
|
537
|
+
hideTab(this, $hide);
|
538
|
+
this.blur();
|
539
|
+
return false;
|
540
|
+
} else if (!$hide.length) {
|
541
|
+
self.$panels.stop();
|
542
|
+
var a = this;
|
543
|
+
self.load(self.$tabs.index(this), function() {
|
544
|
+
$li.addClass(o.selectedClass).addClass(o.unselectClass);
|
545
|
+
showTab(a, $show);
|
546
|
+
});
|
547
|
+
this.blur();
|
548
|
+
return false;
|
549
|
+
}
|
550
|
+
}
|
551
|
+
|
552
|
+
if (o.cookie)
|
553
|
+
$.cookie('ui-tabs' + $.data(self.element), self.options.selected, o.cookie);
|
554
|
+
|
555
|
+
// stop possibly running animations
|
556
|
+
self.$panels.stop();
|
557
|
+
|
558
|
+
// show new tab
|
559
|
+
if ($show.length) {
|
560
|
+
|
561
|
+
// prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled
|
562
|
+
/*if ($.browser.msie && o.bookmarkable) {
|
563
|
+
var showId = this.hash.replace('#', '');
|
564
|
+
$show.attr('id', '');
|
565
|
+
setTimeout(function() {
|
566
|
+
$show.attr('id', showId); // restore id
|
567
|
+
}, 0);
|
568
|
+
}*/
|
569
|
+
|
570
|
+
var a = this;
|
571
|
+
self.load(self.$tabs.index(this), $hide.length ?
|
572
|
+
function() {
|
573
|
+
switchTab(a, $li, $hide, $show);
|
574
|
+
} :
|
575
|
+
function() {
|
576
|
+
$li.addClass(o.selectedClass);
|
577
|
+
showTab(a, $show);
|
578
|
+
}
|
579
|
+
);
|
580
|
+
|
581
|
+
// Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash
|
582
|
+
/*var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0;
|
583
|
+
var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0;
|
584
|
+
setTimeout(function() {
|
585
|
+
scrollTo(scrollX, scrollY);
|
586
|
+
}, 0);*/
|
587
|
+
|
588
|
+
} else
|
589
|
+
throw 'jQuery UI Tabs: Mismatching fragment identifier.';
|
590
|
+
|
591
|
+
// Prevent IE from keeping other link focussed when using the back button
|
592
|
+
// and remove dotted border from clicked link. This is controlled in modern
|
593
|
+
// browsers via CSS, also blur removes focus from address bar in Firefox
|
594
|
+
// which can become a usability and annoying problem with tabsRotate.
|
595
|
+
if ($.browser.msie)
|
596
|
+
this.blur();
|
597
|
+
|
598
|
+
//return o.bookmarkable && !!trueClick; // convert trueClick == undefined to Boolean required in IE
|
599
|
+
return false;
|
600
|
+
|
601
|
+
});
|
602
|
+
|
603
|
+
// disable click if event is configured to something else
|
604
|
+
if (!(/^click/).test(o.event))
|
605
|
+
this.$tabs.bind('click.tabs', function() { return false; });
|
606
|
+
|
607
|
+
},
|
608
|
+
add: function(url, label, index) {
|
609
|
+
if (index == undefined)
|
610
|
+
index = this.$tabs.length; // append by default
|
611
|
+
|
612
|
+
var o = this.options;
|
613
|
+
var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label));
|
614
|
+
$li.data('destroy.tabs', true);
|
615
|
+
|
616
|
+
var id = url.indexOf('#') == 0 ? url.replace('#', '') : this.tabId( $('a:first-child', $li)[0] );
|
617
|
+
|
618
|
+
// try to find an existing element before creating a new one
|
619
|
+
var $panel = $('#' + id);
|
620
|
+
if (!$panel.length) {
|
621
|
+
$panel = $(o.panelTemplate).attr('id', id)
|
622
|
+
.addClass(o.hideClass)
|
623
|
+
.data('destroy.tabs', true);
|
624
|
+
}
|
625
|
+
$panel.addClass(o.panelClass);
|
626
|
+
if (index >= this.$lis.length) {
|
627
|
+
$li.appendTo(this.element);
|
628
|
+
$panel.appendTo(this.element[0].parentNode);
|
629
|
+
} else {
|
630
|
+
$li.insertBefore(this.$lis[index]);
|
631
|
+
$panel.insertBefore(this.$panels[index]);
|
632
|
+
}
|
633
|
+
|
634
|
+
o.disabled = $.map(o.disabled,
|
635
|
+
function(n, i) { return n >= index ? ++n : n });
|
636
|
+
|
637
|
+
this.tabify();
|
638
|
+
|
639
|
+
if (this.$tabs.length == 1) {
|
640
|
+
$li.addClass(o.selectedClass);
|
641
|
+
$panel.removeClass(o.hideClass);
|
642
|
+
var href = $.data(this.$tabs[0], 'load.tabs');
|
643
|
+
if (href)
|
644
|
+
this.load(index, href);
|
645
|
+
}
|
646
|
+
|
647
|
+
// callback
|
648
|
+
this.element.triggerHandler('tabsadd',
|
649
|
+
[this.fakeEvent('tabsadd'), this.ui(this.$tabs[index], this.$panels[index])], o.add
|
650
|
+
);
|
651
|
+
},
|
652
|
+
remove: function(index) {
|
653
|
+
var o = this.options, $li = this.$lis.eq(index).remove(),
|
654
|
+
$panel = this.$panels.eq(index).remove();
|
655
|
+
|
656
|
+
// If selected tab was removed focus tab to the right or
|
657
|
+
// in case the last tab was removed the tab to the left.
|
658
|
+
if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1)
|
659
|
+
this.select(index + (index + 1 < this.$tabs.length ? 1 : -1));
|
660
|
+
|
661
|
+
o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
|
662
|
+
function(n, i) { return n >= index ? --n : n });
|
663
|
+
|
664
|
+
this.tabify();
|
665
|
+
|
666
|
+
// callback
|
667
|
+
this.element.triggerHandler('tabsremove',
|
668
|
+
[this.fakeEvent('tabsremove'), this.ui($li.find('a')[0], $panel[0])], o.remove
|
669
|
+
);
|
670
|
+
},
|
671
|
+
enable: function(index) {
|
672
|
+
var o = this.options;
|
673
|
+
if ($.inArray(index, o.disabled) == -1)
|
674
|
+
return;
|
675
|
+
|
676
|
+
var $li = this.$lis.eq(index).removeClass(o.disabledClass);
|
677
|
+
if ($.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2...
|
678
|
+
$li.css('display', 'inline-block');
|
679
|
+
setTimeout(function() {
|
680
|
+
$li.css('display', 'block');
|
681
|
+
}, 0);
|
682
|
+
}
|
683
|
+
|
684
|
+
o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });
|
685
|
+
|
686
|
+
// callback
|
687
|
+
this.element.triggerHandler('tabsenable',
|
688
|
+
[this.fakeEvent('tabsenable'), this.ui(this.$tabs[index], this.$panels[index])], o.enable
|
689
|
+
);
|
690
|
+
|
691
|
+
},
|
692
|
+
disable: function(index) {
|
693
|
+
var self = this, o = this.options;
|
694
|
+
if (index != o.selected) { // cannot disable already selected tab
|
695
|
+
this.$lis.eq(index).addClass(o.disabledClass);
|
696
|
+
|
697
|
+
o.disabled.push(index);
|
698
|
+
o.disabled.sort();
|
699
|
+
|
700
|
+
// callback
|
701
|
+
this.element.triggerHandler('tabsdisable',
|
702
|
+
[this.fakeEvent('tabsdisable'), this.ui(this.$tabs[index], this.$panels[index])], o.disable
|
703
|
+
);
|
704
|
+
}
|
705
|
+
},
|
706
|
+
select: function(index) {
|
707
|
+
if (typeof index == 'string')
|
708
|
+
index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] );
|
709
|
+
this.$tabs.eq(index).trigger(this.options.event);
|
710
|
+
},
|
711
|
+
load: function(index, callback) { // callback is for internal usage only
|
712
|
+
|
713
|
+
var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0],
|
714
|
+
bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs');
|
715
|
+
|
716
|
+
callback = callback || function() {};
|
717
|
+
|
718
|
+
// no remote or from cache - just finish with callback
|
719
|
+
if (!url || !bypassCache && $.data(a, 'cache.tabs')) {
|
720
|
+
callback();
|
721
|
+
return;
|
722
|
+
}
|
723
|
+
|
724
|
+
// load remote from here on
|
725
|
+
|
726
|
+
var inner = function(parent) {
|
727
|
+
var $parent = $(parent), $inner = $parent.find('*:last');
|
728
|
+
return $inner.length && $inner.is(':not(img)') && $inner || $parent;
|
729
|
+
};
|
730
|
+
var cleanup = function() {
|
731
|
+
self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass)
|
732
|
+
.each(function() {
|
733
|
+
if (o.spinner)
|
734
|
+
inner(this).parent().html(inner(this).data('label.tabs'));
|
735
|
+
});
|
736
|
+
self.xhr = null;
|
737
|
+
};
|
738
|
+
|
739
|
+
if (o.spinner) {
|
740
|
+
var label = inner(a).html();
|
741
|
+
inner(a).wrapInner('<em></em>')
|
742
|
+
.find('em').data('label.tabs', label).html(o.spinner);
|
743
|
+
}
|
744
|
+
|
745
|
+
var ajaxOptions = $.extend({}, o.ajaxOptions, {
|
746
|
+
url: url,
|
747
|
+
success: function(r, s) {
|
748
|
+
$(a.hash).html(r);
|
749
|
+
cleanup();
|
750
|
+
|
751
|
+
if (o.cache)
|
752
|
+
$.data(a, 'cache.tabs', true); // if loaded once do not load them again
|
753
|
+
|
754
|
+
// callbacks
|
755
|
+
$(self.element).triggerHandler('tabsload',
|
756
|
+
[self.fakeEvent('tabsload'), self.ui(self.$tabs[index], self.$panels[index])], o.load
|
757
|
+
);
|
758
|
+
o.ajaxOptions.success && o.ajaxOptions.success(r, s);
|
759
|
+
|
760
|
+
// This callback is required because the switch has to take
|
761
|
+
// place after loading has completed. Call last in order to
|
762
|
+
// fire load before show callback...
|
763
|
+
callback();
|
764
|
+
}
|
765
|
+
});
|
766
|
+
if (this.xhr) {
|
767
|
+
// terminate pending requests from other tabs and restore tab label
|
768
|
+
this.xhr.abort();
|
769
|
+
cleanup();
|
770
|
+
}
|
771
|
+
$a.addClass(o.loadingClass);
|
772
|
+
setTimeout(function() { // timeout is again required in IE, "wait" for id being restored
|
773
|
+
self.xhr = $.ajax(ajaxOptions);
|
774
|
+
}, 0);
|
775
|
+
|
776
|
+
},
|
777
|
+
url: function(index, url) {
|
778
|
+
this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url);
|
779
|
+
},
|
780
|
+
destroy: function() {
|
781
|
+
var o = this.options;
|
782
|
+
this.element.unbind('.tabs')
|
783
|
+
.removeClass(o.navClass).removeData('tabs');
|
784
|
+
this.$tabs.each(function() {
|
785
|
+
var href = $.data(this, 'href.tabs');
|
786
|
+
if (href)
|
787
|
+
this.href = href;
|
788
|
+
var $this = $(this).unbind('.tabs');
|
789
|
+
$.each(['href', 'load', 'cache'], function(i, prefix) {
|
790
|
+
$this.removeData(prefix + '.tabs');
|
791
|
+
});
|
792
|
+
});
|
793
|
+
this.$lis.add(this.$panels).each(function() {
|
794
|
+
if ($.data(this, 'destroy.tabs'))
|
795
|
+
$(this).remove();
|
796
|
+
else
|
797
|
+
$(this).removeClass([o.selectedClass, o.unselectClass,
|
798
|
+
o.disabledClass, o.panelClass, o.hideClass].join(' '));
|
799
|
+
});
|
800
|
+
},
|
801
|
+
fakeEvent: function(type) {
|
802
|
+
return $.event.fix({
|
803
|
+
type: type,
|
804
|
+
target: this.element[0]
|
805
|
+
});
|
806
|
+
}
|
807
|
+
});
|
808
|
+
|
809
|
+
$.ui.tabs.defaults = {
|
810
|
+
// basic setup
|
811
|
+
unselect: false,
|
812
|
+
event: 'click',
|
813
|
+
disabled: [],
|
814
|
+
cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
|
815
|
+
// TODO history: false,
|
816
|
+
|
817
|
+
// Ajax
|
818
|
+
spinner: 'Loading…',
|
819
|
+
cache: false,
|
820
|
+
idPrefix: 'ui-tabs-',
|
821
|
+
ajaxOptions: {},
|
822
|
+
|
823
|
+
// animations
|
824
|
+
fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
|
825
|
+
|
826
|
+
// templates
|
827
|
+
tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>',
|
828
|
+
panelTemplate: '<div></div>',
|
829
|
+
|
830
|
+
// CSS classes
|
831
|
+
navClass: 'ui-tabs-nav',
|
832
|
+
selectedClass: 'ui-tabs-selected',
|
833
|
+
unselectClass: 'ui-tabs-unselect',
|
834
|
+
disabledClass: 'ui-tabs-disabled',
|
835
|
+
panelClass: 'ui-tabs-panel',
|
836
|
+
hideClass: 'ui-tabs-hide',
|
837
|
+
loadingClass: 'ui-tabs-loading'
|
838
|
+
};
|
839
|
+
|
840
|
+
$.ui.tabs.getter = "length";
|
841
|
+
|
842
|
+
/*
|
843
|
+
* Tabs Extensions
|
844
|
+
*/
|
845
|
+
|
846
|
+
/*
|
847
|
+
* Rotate
|
848
|
+
*/
|
849
|
+
$.extend($.ui.tabs.prototype, {
|
850
|
+
rotation: null,
|
851
|
+
rotate: function(ms, continuing) {
|
852
|
+
|
853
|
+
continuing = continuing || false;
|
854
|
+
|
855
|
+
var self = this, t = this.options.selected;
|
856
|
+
|
857
|
+
function start() {
|
858
|
+
self.rotation = setInterval(function() {
|
859
|
+
t = ++t < self.$tabs.length ? t : 0;
|
860
|
+
self.select(t);
|
861
|
+
}, ms);
|
862
|
+
}
|
863
|
+
|
864
|
+
function stop(e) {
|
865
|
+
if (!e || e.clientX) { // only in case of a true click
|
866
|
+
clearInterval(self.rotation);
|
867
|
+
}
|
868
|
+
}
|
869
|
+
|
870
|
+
// start interval
|
871
|
+
if (ms) {
|
872
|
+
start();
|
873
|
+
if (!continuing)
|
874
|
+
this.$tabs.bind(this.options.event, stop);
|
875
|
+
else
|
876
|
+
this.$tabs.bind(this.options.event, function() {
|
877
|
+
stop();
|
878
|
+
t = self.options.selected;
|
879
|
+
start();
|
880
|
+
});
|
881
|
+
}
|
882
|
+
// stop interval
|
883
|
+
else {
|
884
|
+
stop();
|
885
|
+
this.$tabs.unbind(this.options.event, stop);
|
886
|
+
}
|
887
|
+
}
|
888
|
+
});
|
889
|
+
|
890
|
+
})(jQuery);
|