2 * Description: implements a wizard-like interface
3 * Copyright (c) 2013 UPMC Sorbonne Universite
4 * Based on SmartWizard 2.0 plugin by Dipu (http://www.techlaboratory.net)
9 * It's a best practice to pass jQuery to an IIFE (Immediately Invoked Function
10 * Expression) that maps it to the dollar sign so it can't be overwritten by
11 * another library in the scope of its execution.
15 /***************************************************************************
16 * Method calling logic
17 ***************************************************************************/
19 $.fn.Wizard = function( method ) {
20 if ( methods[method] ) {
21 return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
22 } else if ( typeof method === 'object' || ! method ) {
23 return methods.init.apply( this, arguments );
25 //$.error( 'Method ' + method + ' does not exist on jQuery.Wizard' );
30 /***************************************************************************
32 ***************************************************************************/
37 * @brief Plugin initialization
38 * @param options : an associative array of setting values
39 * @return : a jQuery collection of objects on which the plugin is
40 * applied, which allows to maintain chainability of calls
42 init : function ( options ) {
44 /* Default settings */
45 var options = $.extend({}, $.fn.Wizard.defaults, options);
47 return this.each(function() {
49 var obj = $(wizard, this);
51 /* An object that will hold private variables and methods */
52 var form = new Wizard(options, obj);
53 $this.data('Wizard', form);
57 // // Initialize the smart-wizard third-party jquery plugin
58 // $(wizard, this).smartWizard({
59 // selected : options.start_step - 1,
61 // onLeaveStep : leaveAStepCallback,
62 //// onFinish : onFinishCallback
65 // // XXX Mark some steps as done !
66 // $(wizard, this).smartWizard('setDone',{stepnum: 1, isdone:true});
68 // function leaveAStepCallback(obj){
69 // var step_num= obj.attr('rel')-1; // get the current step number
70 // func = options.validate_step_js[step_num];
71 // if (func == 'null')
73 // return window[func]();
76 // function onFinishCallback(){
77 // window.location.href('/');
86 /***************************************************************************
88 ***************************************************************************/
91 * \param options (dict) : a dictionary of options
92 * \param obj (jQuery ref) : handler to the wizard plugin
94 function Wizard(options, obj) {
96 /* save a reference to this */
99 /* member variables */
100 this.options = options;
105 * \brief get a handle on a wizard step by id
106 \param stepIdx (integer) : step identifier (0-based)
107 \returns jQuery selector
109 this.get_step = function(stepIdx)
111 return $('.plugin', $(stepIdx.attr('href')));
114 this.get_plugin = function(step)
116 return step.data().plugin;
119 this.onLeaveStep = function(stepIdx)
121 /* does the plugin has a validate entry ? */
122 var step = this.get_step(stepIdx);
123 var plugin = this.get_plugin(step);
124 if (plugin.validate) {
130 this.init1 = function() {
131 $this.curStepIdx = options.selected;
132 $this.steps = $("ul > li > a[href^='#step-']", obj); // Get all anchors in this array
133 $this.contentWidth = 0;
134 //this.loader,msgBox,elmActionBar,elmStepContainer,btNext,btPrevious,btFinish;
136 $this.elmActionBar = $('.actionBar',obj);
137 if($this.elmActionBar.length == 0){
138 $this.elmActionBar = $('<div></div>').addClass("actionBar");
141 $this.msgBox = $('.msgBox',obj);
142 if($this.msgBox.length == 0){
143 $this.msgBox = $('<div class="msgBox"><div class="content"></div><a href="#" class="close">X</a></div>');
144 $this.elmActionBar.append($this.msgBox);
147 $('.close',$this.msgBox).click(function() {
148 $this.msgBox.fadeOut("normal");
153 this.init = function() {
157 this.options.onLeaveStep = this.onLeaveStep;
159 var allDivs =obj.children('div'); //$("div", obj);
160 obj.children('ul').addClass("anchor");
161 allDivs.addClass("content");
163 loader = $('<div>Loading</div>').addClass("loader");
164 elmActionBar = $('<div></div>').addClass("actionBar");
165 elmStepContainer = $('<div></div>').addClass("stepContainer");
166 btNext = $('<a>'+options.labelNext+'</a>').attr("href","#").addClass("buttonNext");
167 btPrevious = $('<a>'+options.labelPrevious+'</a>').attr("href","#").addClass("buttonPrevious");
168 btFinish = $('<a>'+options.labelFinish+'</a>').attr("href","#").addClass("buttonFinish");
170 // highlight steps with errors
171 if(options.errorSteps && options.errorSteps.length>0){
172 $.each(options.errorSteps, function(i, n){
173 $this.setError(n,true);
178 elmStepContainer.append(allDivs);
179 elmActionBar.append(loader);
180 obj.append(elmStepContainer);
181 obj.append(elmActionBar);
182 if (options.includeFinishButton) {
183 elmActionBar.append(btFinish);
185 elmActionBar.append(btNext).append(btPrevious);
186 contentWidth = elmStepContainer.width();
188 $(btNext).click(function() {
189 if($(this).hasClass('buttonDisabled')){
192 $this.doForwardProgress();
195 $(btPrevious).click(function() {
196 if($(this).hasClass('buttonDisabled')){
199 $this.doBackwardProgress();
202 $(btFinish).click(function() {
203 if(!$(this).hasClass('buttonDisabled')){
204 if($.isFunction(options.onFinish)) {
205 if(!options.onFinish.call(this,$(this.steps))){
209 var frm = $(obj).parents('form');
210 if(frm && frm.length){
219 $(this.steps).bind("click", function(e){
220 if(steps.index(this) == $this.curStepIdx){
223 var nextStepIdx = steps.index(this);
224 var isDone = steps.eq(nextStepIdx).attr("isDone") - 0;
226 $this.LoadContent(nextStepIdx);
231 // Enable keyboard navigation
232 if(options.keyNavigation){
233 $(document).keyup(function(e){
234 if(e.which==39){ // Right Arrow
235 $this.doForwardProgress();
236 }else if(e.which==37){ // Left Arrow
237 $this.doBackwardProgress();
243 // Show the first slected step
244 this.LoadContent($this.curStepIdx);
247 this.prepareSteps = function() {
248 if (!options.enableAllSteps) {
249 $(this.steps, obj).removeClass("selected").removeClass("done").addClass("disabled");
250 $(this.steps, obj).attr("isDone",0);
252 $(this.steps, obj).removeClass("selected").removeClass("disabled").addClass("done");
253 $(this.steps, obj).attr("isDone",1);
256 $(this.steps, obj).each(function(i){
257 $($(this).attr("href"), obj).hide();
258 $(this).attr("rel",i+1);
262 this.LoadContent = function(stepIdx) {
263 var selStep = this.steps.eq(stepIdx);
264 var ajaxurl = options.contentURL;
265 var hasContent = selStep.data('hasContent');
267 if(ajaxurl && ajaxurl.length>0){
268 if(options.contentCache && hasContent){
269 this.showStep(stepIdx);
274 data: ({step_number : stepNum}),
276 beforeSend: function(){ loader.show(); },
277 error: function(){loader.hide();},
278 success: function(res){
280 if(res && res.length>0){
281 selStep.data('hasContent',true);
282 $($(selStep, obj).attr("href"), obj).html(res);
283 this.showStep(stepIdx);
289 this.showStep(stepIdx);
291 } // this.LoadContent
293 this.showStep = function(stepIdx)
295 var selStep = this.steps.eq(stepIdx);
296 var curStep = this.steps.eq($this.curStepIdx);
299 if(stepIdx != $this.curStepIdx){
300 if($.isFunction(options.onLeaveStep)) {
301 if(!options.onLeaveStep.call(this,$(curStep))){
306 if (options.updateHeight)
307 elmStepContainer.height($($(selStep, obj).attr("href"), obj).outerHeight());
308 if(options.transitionEffect == 'slide'){
309 $($(curStep, obj).attr("href"), obj).slideUp("fast",function(e){
310 $($(selStep, obj).attr("href"), obj).slideDown("fast");
311 $this.curStepIdx = stepIdx;
312 $this.SetupStep(curStep,selStep);
314 } else if(options.transitionEffect == 'fade'){
315 $($(curStep, obj).attr("href"), obj).fadeOut("fast",function(e){
316 $($(selStep, obj).attr("href"), obj).fadeIn("fast");
317 $this.curStepIdx = stepIdx;
318 $this.SetupStep(curStep,selStep);
320 } else if(options.transitionEffect == 'slideleft'){
322 var curElementLeft = 0;
323 if(stepIdx > $this.curStepIdx){
324 nextElmLeft1 = contentWidth + 10;
326 curElementLeft = 0 - $($(curStep, obj).attr("href"), obj).outerWidth();
328 nextElmLeft1 = 0 - $($(selStep, obj).attr("href"), obj).outerWidth() + 20;
330 curElementLeft = 10 + $($(curStep, obj).attr("href"), obj).outerWidth();
332 if(stepIdx == $this.curStepIdx){
333 nextElmLeft1 = $($(selStep, obj).attr("href"), obj).outerWidth() + 20;
335 curElementLeft = 0 - $($(curStep, obj).attr("href"), obj).outerWidth();
337 $($(curStep, obj).attr("href"), obj).animate({left:curElementLeft},"fast",function(e){
338 $($(curStep, obj).attr("href"), obj).hide();
342 $($(selStep, obj).attr("href"), obj).css("left",nextElmLeft1);
343 $($(selStep, obj).attr("href"), obj).show();
344 $($(selStep, obj).attr("href"), obj).animate({left:nextElmLeft2},"fast",function(e){
345 $this.curStepIdx = stepIdx;
346 $this.SetupStep(curStep,selStep);
349 $($(curStep, obj).attr("href"), obj).hide();
350 $($(selStep, obj).attr("href"), obj).show();
351 $this.curStepIdx = stepIdx;
352 $this.SetupStep(curStep,selStep);
357 this.SetupStep = function(curStep,selStep)
359 $(curStep, obj).removeClass("selected");
360 $(curStep, obj).addClass("done");
362 $(selStep, obj).removeClass("disabled");
363 $(selStep, obj).removeClass("done");
364 $(selStep, obj).addClass("selected");
365 $(selStep, obj).attr("isDone",1);
366 $this.adjustButton();
367 if($.isFunction(options.onShowStep)) {
368 if(!options.onShowStep.call(this,$(selStep))){
374 this.doForwardProgress = function()
376 var nextStepIdx = $this.curStepIdx + 1;
377 if(this.steps.length <= nextStepIdx){
378 if(!options.cycleSteps){
383 this.LoadContent(nextStepIdx);
386 this.doBackwardProgress = function()
388 var nextStepIdx = $this.curStepIdx-1;
390 if(!options.cycleSteps){
393 nextStepIdx = this.steps.length - 1;
395 this.LoadContent(nextStepIdx);
398 this.adjustButton = function()
400 if(!options.cycleSteps){
401 if(0 >= $this.curStepIdx){
402 $(btPrevious).addClass("buttonDisabled");
404 $(btPrevious).removeClass("buttonDisabled");
406 if(($this.steps.length-1) <= $this.curStepIdx){
407 $(btNext).addClass("buttonDisabled");
409 $(btNext).removeClass("buttonDisabled");
413 if(!$this.steps.hasClass('disabled') || options.enableFinishButton){
414 $(btFinish).removeClass("buttonDisabled");
416 $(btFinish).addClass("buttonDisabled");
420 this.showMessage = function(msg)
422 $('.content',msgBox).html(msg);
426 this.setError = function(stepnum,iserror)
429 $(this.steps.eq(stepnum-1), obj).addClass('error')
431 $(this.steps.eq(stepnum-1), obj).removeClass("error");
435 this.setDone = function(stepnum,isdone)
438 $(this.steps.eq(stepnum-1), obj).removeClass("selected").removeClass("disabled").addClass('done')
440 $(this.steps.eq(stepnum-1), obj).removeClass("done");
447 // Default Properties and Events
448 $.fn.Wizard.defaults = {
449 selected: 0, // Selected Step, 0 = first step
450 keyNavigation: true, // Enable/Disable key navigation(left and right keys are used if enabled)
451 enableAllSteps: false,
453 transitionEffect: 'fade', // Effect on navigation, none/fade/slide/slideleft
454 contentURL:null, // content url, Enables Ajax content loading
455 contentCache:true, // cache step contents, if false content is fetched always from ajax url
456 cycleSteps: false, // cycle step navigation
457 includeFinishButton: true, // whether to show a Finish button
458 enableFinishButton: false, // make finish button enabled always
459 errorSteps:[], // Array Steps with errors
461 labelPrevious:'Previous',
462 labelFinish:'Finish',
463 onLeaveStep: null, // triggers when leaving a step
464 onShowStep: null, // triggers when showing a step
465 onFinish: null // triggers when Finish button is clicked