|
1 /*! |
|
2 * jQuery twitter bootstrap wizard plugin |
|
3 * Examples and documentation at: http://github.com/VinceG/twitter-bootstrap-wizard |
|
4 * version 1.4.2 |
|
5 * Requires jQuery v1.3.2 or later |
|
6 * Supports Bootstrap 2.2.x, 2.3.x, 3.0 |
|
7 * Dual licensed under the MIT and GPL licenses: |
|
8 * http://www.opensource.org/licenses/mit-license.php |
|
9 * http://www.gnu.org/licenses/gpl.html |
|
10 * Authors: Vadim Vincent Gabriel (http://vadimg.com), Jason Gill (www.gilluminate.com) |
|
11 */ |
|
12 ;(function($) { |
|
13 var bootstrapWizardCreate = function(element, options) { |
|
14 var element = $(element); |
|
15 var obj = this; |
|
16 |
|
17 // selector skips any 'li' elements that do not contain a child with a tab data-toggle |
|
18 var baseItemSelector = 'li:has([data-toggle="tab"])'; |
|
19 var historyStack = []; |
|
20 |
|
21 // Merge options with defaults |
|
22 var $settings = $.extend({}, $.fn.bootstrapWizard.defaults, options); |
|
23 var $activeTab = null; |
|
24 var $navigation = null; |
|
25 |
|
26 this.rebindClick = function(selector, fn) |
|
27 { |
|
28 selector.unbind('click', fn).bind('click', fn); |
|
29 } |
|
30 |
|
31 this.fixNavigationButtons = function() { |
|
32 // Get the current active tab |
|
33 if(!$activeTab.length) { |
|
34 // Select first one |
|
35 $navigation.find('a:first').tab('show'); |
|
36 $activeTab = $navigation.find(baseItemSelector + ':first'); |
|
37 } |
|
38 |
|
39 // See if we're currently in the first/last then disable the previous and last buttons |
|
40 $($settings.previousSelector, element).toggleClass('disabled', (obj.firstIndex() >= obj.currentIndex())); |
|
41 $($settings.nextSelector, element).toggleClass('disabled', (obj.currentIndex() >= obj.navigationLength())); |
|
42 $($settings.nextSelector, element).toggleClass('hidden', (obj.currentIndex() >= obj.navigationLength() && $($settings.finishSelector, element).length > 0)); |
|
43 $($settings.lastSelector, element).toggleClass('hidden', (obj.currentIndex() >= obj.navigationLength() && $($settings.finishSelector, element).length > 0)); |
|
44 $($settings.finishSelector, element).toggleClass('hidden', (obj.currentIndex() < obj.navigationLength())); |
|
45 $($settings.backSelector, element).toggleClass('disabled', historyStack.length === 0); |
|
46 $($settings.backSelector, element).toggleClass('hidden', (obj.currentIndex() >= obj.navigationLength() && $($settings.finishSelector, element).length > 0)); |
|
47 |
|
48 // We are unbinding and rebinding to ensure single firing and no double-click errors |
|
49 obj.rebindClick($($settings.nextSelector, element), obj.next); |
|
50 obj.rebindClick($($settings.previousSelector, element), obj.previous); |
|
51 obj.rebindClick($($settings.lastSelector, element), obj.last); |
|
52 obj.rebindClick($($settings.firstSelector, element), obj.first); |
|
53 obj.rebindClick($($settings.finishSelector, element), obj.finish); |
|
54 obj.rebindClick($($settings.backSelector, element), obj.back); |
|
55 |
|
56 if($settings.onTabShow && typeof $settings.onTabShow === 'function' && $settings.onTabShow($activeTab, $navigation, obj.currentIndex())===false){ |
|
57 return false; |
|
58 } |
|
59 }; |
|
60 |
|
61 this.next = function(e) { |
|
62 // If we clicked the last then dont activate this |
|
63 if(element.hasClass('last')) { |
|
64 return false; |
|
65 } |
|
66 |
|
67 if($settings.onNext && typeof $settings.onNext === 'function' && $settings.onNext($activeTab, $navigation, obj.nextIndex())===false){ |
|
68 return false; |
|
69 } |
|
70 |
|
71 var formerIndex = obj.currentIndex(); |
|
72 var $index = obj.nextIndex(); |
|
73 |
|
74 // Did we click the last button |
|
75 if($index > obj.navigationLength()) { |
|
76 } else { |
|
77 historyStack.push(formerIndex); |
|
78 $navigation.find(baseItemSelector + ($settings.withVisible ? ':visible' : '') + ':eq(' + $index + ') a').tab('show'); |
|
79 } |
|
80 }; |
|
81 |
|
82 this.previous = function(e) { |
|
83 // If we clicked the first then dont activate this |
|
84 if(element.hasClass('first')) { |
|
85 return false; |
|
86 } |
|
87 |
|
88 if($settings.onPrevious && typeof $settings.onPrevious === 'function' && $settings.onPrevious($activeTab, $navigation, obj.previousIndex())===false){ |
|
89 return false; |
|
90 } |
|
91 |
|
92 var formerIndex = obj.currentIndex(); |
|
93 var $index = obj.previousIndex(); |
|
94 |
|
95 if($index < 0) { |
|
96 } else { |
|
97 historyStack.push(formerIndex); |
|
98 $navigation.find(baseItemSelector + ($settings.withVisible ? ':visible' : '') + ':eq(' + $index + ') a').tab('show'); |
|
99 } |
|
100 }; |
|
101 |
|
102 this.first = function (e) { |
|
103 if($settings.onFirst && typeof $settings.onFirst === 'function' && $settings.onFirst($activeTab, $navigation, obj.firstIndex())===false){ |
|
104 return false; |
|
105 } |
|
106 |
|
107 // If the element is disabled then we won't do anything |
|
108 if(element.hasClass('disabled')) { |
|
109 return false; |
|
110 } |
|
111 |
|
112 |
|
113 historyStack.push(obj.currentIndex()); |
|
114 $navigation.find(baseItemSelector + ':eq(0) a').tab('show'); |
|
115 }; |
|
116 |
|
117 this.last = function(e) { |
|
118 if($settings.onLast && typeof $settings.onLast === 'function' && $settings.onLast($activeTab, $navigation, obj.lastIndex())===false){ |
|
119 return false; |
|
120 } |
|
121 |
|
122 // If the element is disabled then we won't do anything |
|
123 if(element.hasClass('disabled')) { |
|
124 return false; |
|
125 } |
|
126 |
|
127 historyStack.push(obj.currentIndex()); |
|
128 $navigation.find(baseItemSelector + ':eq(' + obj.navigationLength() + ') a').tab('show'); |
|
129 }; |
|
130 |
|
131 this.finish = function (e) { |
|
132 if ($settings.onFinish && typeof $settings.onFinish === 'function') { |
|
133 $settings.onFinish($activeTab, $navigation, obj.lastIndex()); |
|
134 } |
|
135 }; |
|
136 |
|
137 this.back = function () { |
|
138 if (historyStack.length == 0) { |
|
139 return null; |
|
140 } |
|
141 |
|
142 var formerIndex = historyStack.pop(); |
|
143 if ($settings.onBack && typeof $settings.onBack === 'function' && $settings.onBack($activeTab, $navigation, formerIndex) === false) { |
|
144 historyStack.push(formerIndex); |
|
145 return false; |
|
146 } |
|
147 |
|
148 element.find(baseItemSelector + ':eq(' + formerIndex + ') a').tab('show'); |
|
149 }; |
|
150 |
|
151 this.currentIndex = function() { |
|
152 return $navigation.find(baseItemSelector + ($settings.withVisible ? ':visible' : '')).index($activeTab); |
|
153 }; |
|
154 |
|
155 this.firstIndex = function() { |
|
156 return 0; |
|
157 }; |
|
158 |
|
159 this.lastIndex = function() { |
|
160 return obj.navigationLength(); |
|
161 }; |
|
162 |
|
163 this.getIndex = function(e) { |
|
164 return $navigation.find(baseItemSelector + ($settings.withVisible ? ':visible' : '')).index(e); |
|
165 }; |
|
166 |
|
167 this.nextIndex = function() { |
|
168 var nextIndexCandidate=this.currentIndex(); |
|
169 var nextTabCandidate=null; |
|
170 do { |
|
171 nextIndexCandidate++; |
|
172 nextTabCandidate = $navigation.find(baseItemSelector + ($settings.withVisible ? ':visible' : '') + ":eq(" + nextIndexCandidate + ")"); |
|
173 } while ((nextTabCandidate)&&(nextTabCandidate.hasClass("disabled"))); |
|
174 return nextIndexCandidate; |
|
175 }; |
|
176 this.previousIndex = function() { |
|
177 var prevIndexCandidate=this.currentIndex(); |
|
178 var prevTabCandidate=null; |
|
179 do { |
|
180 prevIndexCandidate--; |
|
181 prevTabCandidate = $navigation.find(baseItemSelector + ($settings.withVisible ? ':visible' : '') + ":eq(" + prevIndexCandidate + ")"); |
|
182 } while ((prevTabCandidate)&&(prevTabCandidate.hasClass("disabled"))); |
|
183 return prevIndexCandidate; |
|
184 }; |
|
185 this.navigationLength = function() { |
|
186 return $navigation.find(baseItemSelector + ($settings.withVisible ? ':visible' : '')).length - 1; |
|
187 }; |
|
188 this.activeTab = function() { |
|
189 return $activeTab; |
|
190 }; |
|
191 this.nextTab = function() { |
|
192 return $navigation.find(baseItemSelector + ':eq('+(obj.currentIndex()+1)+')').length ? $navigation.find(baseItemSelector + ':eq('+(obj.currentIndex()+1)+')') : null; |
|
193 }; |
|
194 this.previousTab = function() { |
|
195 if(obj.currentIndex() <= 0) { |
|
196 return null; |
|
197 } |
|
198 return $navigation.find(baseItemSelector + ':eq('+parseInt(obj.currentIndex()-1)+')'); |
|
199 }; |
|
200 this.show = function(index) { |
|
201 var tabToShow = isNaN(index) ? |
|
202 element.find(baseItemSelector + ' a[href="#' + index + '"]') : |
|
203 element.find(baseItemSelector + ':eq(' + index + ') a'); |
|
204 if (tabToShow.length > 0) { |
|
205 historyStack.push(obj.currentIndex()); |
|
206 tabToShow.tab('show'); |
|
207 } |
|
208 }; |
|
209 this.disable = function (index) { |
|
210 $navigation.find(baseItemSelector + ':eq('+index+')').addClass('disabled'); |
|
211 }; |
|
212 this.enable = function(index) { |
|
213 $navigation.find(baseItemSelector + ':eq('+index+')').removeClass('disabled'); |
|
214 }; |
|
215 this.hide = function(index) { |
|
216 $navigation.find(baseItemSelector + ':eq('+index+')').hide(); |
|
217 }; |
|
218 this.display = function(index) { |
|
219 $navigation.find(baseItemSelector + ':eq('+index+')').show(); |
|
220 }; |
|
221 this.remove = function(args) { |
|
222 var $index = args[0]; |
|
223 var $removeTabPane = typeof args[1] != 'undefined' ? args[1] : false; |
|
224 var $item = $navigation.find(baseItemSelector + ':eq('+$index+')'); |
|
225 |
|
226 // Remove the tab pane first if needed |
|
227 if($removeTabPane) { |
|
228 var $href = $item.find('a').attr('href'); |
|
229 $($href).remove(); |
|
230 } |
|
231 |
|
232 // Remove menu item |
|
233 $item.remove(); |
|
234 }; |
|
235 |
|
236 var innerTabClick = function (e) { |
|
237 // Get the index of the clicked tab |
|
238 var $ul = $navigation.find(baseItemSelector); |
|
239 var clickedIndex = $ul.index($(e.currentTarget).parent(baseItemSelector)); |
|
240 var $clickedTab = $( $ul[clickedIndex] ); |
|
241 if($settings.onTabClick && typeof $settings.onTabClick === 'function' && $settings.onTabClick($activeTab, $navigation, obj.currentIndex(), clickedIndex, $clickedTab)===false){ |
|
242 return false; |
|
243 } |
|
244 }; |
|
245 |
|
246 var innerTabShown = function (e) { |
|
247 var $element = $(e.target).parent(); |
|
248 var nextTab = $navigation.find(baseItemSelector).index($element); |
|
249 |
|
250 // If it's disabled then do not change |
|
251 if($element.hasClass('disabled')) { |
|
252 return false; |
|
253 } |
|
254 |
|
255 if($settings.onTabChange && typeof $settings.onTabChange === 'function' && $settings.onTabChange($activeTab, $navigation, obj.currentIndex(), nextTab)===false){ |
|
256 return false; |
|
257 } |
|
258 |
|
259 $activeTab = $element; // activated tab |
|
260 obj.fixNavigationButtons(); |
|
261 }; |
|
262 |
|
263 this.resetWizard = function() { |
|
264 |
|
265 // remove the existing handlers |
|
266 $('a[data-toggle="tab"]', $navigation).off('click', innerTabClick); |
|
267 $('a[data-toggle="tab"]', $navigation).off('show show.bs.tab', innerTabShown); |
|
268 |
|
269 // reset elements based on current state of the DOM |
|
270 $navigation = element.find('ul:first', element); |
|
271 $activeTab = $navigation.find(baseItemSelector + '.active', element); |
|
272 |
|
273 // re-add handlers |
|
274 $('a[data-toggle="tab"]', $navigation).on('click', innerTabClick); |
|
275 $('a[data-toggle="tab"]', $navigation).on('show show.bs.tab', innerTabShown); |
|
276 |
|
277 obj.fixNavigationButtons(); |
|
278 }; |
|
279 |
|
280 $navigation = element.find('ul:first', element); |
|
281 $activeTab = $navigation.find(baseItemSelector + '.active', element); |
|
282 |
|
283 if(!$navigation.hasClass($settings.tabClass)) { |
|
284 $navigation.addClass($settings.tabClass); |
|
285 } |
|
286 |
|
287 // Load onInit |
|
288 if($settings.onInit && typeof $settings.onInit === 'function'){ |
|
289 $settings.onInit($activeTab, $navigation, 0); |
|
290 } |
|
291 |
|
292 // Load onShow |
|
293 if($settings.onShow && typeof $settings.onShow === 'function'){ |
|
294 $settings.onShow($activeTab, $navigation, obj.nextIndex()); |
|
295 } |
|
296 |
|
297 $('a[data-toggle="tab"]', $navigation).on('click', innerTabClick); |
|
298 |
|
299 // attach to both show and show.bs.tab to support Bootstrap versions 2.3.2 and 3.0.0 |
|
300 $('a[data-toggle="tab"]', $navigation).on('show show.bs.tab', innerTabShown); |
|
301 }; |
|
302 $.fn.bootstrapWizard = function(options) { |
|
303 //expose methods |
|
304 if (typeof options == 'string') { |
|
305 var args = Array.prototype.slice.call(arguments, 1) |
|
306 if(args.length === 1) { |
|
307 args.toString(); |
|
308 } |
|
309 return this.data('bootstrapWizard')[options](args); |
|
310 } |
|
311 return this.each(function(index){ |
|
312 var element = $(this); |
|
313 // Return early if this element already has a plugin instance |
|
314 if (element.data('bootstrapWizard')) return; |
|
315 // pass options to plugin constructor |
|
316 var wizard = new bootstrapWizardCreate(element, options); |
|
317 // Store plugin object in this element's data |
|
318 element.data('bootstrapWizard', wizard); |
|
319 // and then trigger initial change |
|
320 wizard.fixNavigationButtons(); |
|
321 }); |
|
322 }; |
|
323 |
|
324 // expose options |
|
325 $.fn.bootstrapWizard.defaults = { |
|
326 withVisible: true, |
|
327 tabClass: 'nav nav-pills', |
|
328 nextSelector: '.wizard li.next', |
|
329 previousSelector: '.wizard li.previous', |
|
330 firstSelector: '.wizard li.first', |
|
331 lastSelector: '.wizard li.last', |
|
332 finishSelector: '.wizard li.finish', |
|
333 backSelector: '.wizard li.back', |
|
334 onShow: null, |
|
335 onInit: null, |
|
336 onNext: null, |
|
337 onPrevious: null, |
|
338 onLast: null, |
|
339 onFirst: null, |
|
340 onFinish: null, |
|
341 onBack: null, |
|
342 onTabChange: null, |
|
343 onTabClick: null, |
|
344 onTabShow: null |
|
345 }; |
|
346 |
|
347 })(jQuery); |