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) { return $('.plugin', $(stepIdx.attr('href'))); }
111 this.get_plugin = function(step) { return step.data().plugin; }
113 this.init1 = function() {
114 $this.curStepIdx = options.selected;
115 $this.steps = $("ul > li > a[href^='#step-']", obj); // Get all anchors in this array
116 $this.contentWidth = 0;
117 //this.loader,msgBox,elmActionBar,elmStepContainer,btNext,btPrevious,btFinish;
119 $this.elmActionBar = $('.actionBar',obj);
120 if($this.elmActionBar.length == 0){
121 $this.elmActionBar = $('<div></div>').addClass("actionBar");
124 $this.msgBox = $('.msgBox',obj);
125 if($this.msgBox.length == 0){
126 $this.msgBox = $('<div class="msgBox"><div class="content"></div><a href="#" class="close">X</a></div>');
127 $this.elmActionBar.append($this.msgBox);
130 $('.close',$this.msgBox).click(function() {
131 $this.msgBox.fadeOut("normal");
136 this.init = function() {
139 var allDivs =obj.children('div'); //$("div", obj);
140 obj.children('ul').addClass("anchor");
141 allDivs.addClass("content");
143 loader = $('<div>Loading</div>').addClass("loader");
144 elmActionBar = $('<div></div>').addClass("actionBar");
145 elmStepContainer = $('<div></div>').addClass("stepContainer");
146 btNext = $('<a>'+options.labelNext+'</a>').attr("href","#").addClass("buttonNext");
147 btPrevious = $('<a>'+options.labelPrevious+'</a>').attr("href","#").addClass("buttonPrevious");
148 btFinish = $('<a>'+options.labelFinish+'</a>').attr("href","#").addClass("buttonFinish");
150 // highlight steps with errors
151 if(options.errorSteps && options.errorSteps.length>0){
152 $.each(options.errorSteps, function(i, n){
153 $this.setError(n,true);
158 elmStepContainer.append(allDivs);
159 elmActionBar.append(loader);
160 obj.append(elmStepContainer);
161 obj.append(elmActionBar);
162 if (options.includeFinishButton) {
163 elmActionBar.append(btFinish);
165 elmActionBar.append(btNext).append(btPrevious);
166 contentWidth = elmStepContainer.width();
168 $(btNext).click(function() {
169 if($(this).hasClass('buttonDisabled')){
172 $this.doForwardProgress();
175 $(btPrevious).click(function() {
176 if($(this).hasClass('buttonDisabled')){
179 $this.doBackwardProgress();
182 $(btFinish).click(function() {
183 if(!$(this).hasClass('buttonDisabled')){
184 if($.isFunction(options.onFinish)) {
185 if(!options.onFinish.call(this,$(this.steps))){
189 var frm = $(obj).parents('form');
190 if(frm && frm.length){
199 $(this.steps).bind("click", function(e){
200 if(steps.index(this) == $this.curStepIdx){
203 var nextStepIdx = steps.index(this);
204 var isDone = steps.eq(nextStepIdx).attr("isDone") - 0;
206 $this.LoadContent(nextStepIdx);
211 // Enable keyboard navigation
212 if(options.keyNavigation){
213 $(document).keyup(function(e){
214 if(e.which==39){ // Right Arrow
215 $this.doForwardProgress();
216 }else if(e.which==37){ // Left Arrow
217 $this.doBackwardProgress();
223 // Show the first slected step
224 this.LoadContent($this.curStepIdx);
227 this.prepareSteps = function() {
228 if (!options.enableAllSteps) {
229 $(this.steps, obj).removeClass("selected").removeClass("done").addClass("disabled");
230 $(this.steps, obj).attr("isDone",0);
232 $(this.steps, obj).removeClass("selected").removeClass("disabled").addClass("done");
233 $(this.steps, obj).attr("isDone",1);
236 $(this.steps, obj).each(function(i){
237 $($(this).attr("href"), obj).hide();
238 $(this).attr("rel",i+1);
242 this.LoadContent = function(stepIdx) {
243 var selStep = this.steps.eq(stepIdx);
244 var ajaxurl = options.contentURL;
245 var hasContent = selStep.data('hasContent');
247 if(ajaxurl && ajaxurl.length>0){
248 if(options.contentCache && hasContent){
249 this.showStep(stepIdx);
254 data: ({step_number : stepNum}),
256 beforeSend: function(){ loader.show(); },
257 error: function(){loader.hide();},
258 success: function(res){
260 if(res && res.length>0){
261 selStep.data('hasContent',true);
262 $($(selStep, obj).attr("href"), obj).html(res);
263 this.showStep(stepIdx);
269 this.showStep(stepIdx);
271 } // this.LoadContent
273 this.showStep = function(stepIdx)
275 var selStep = this.steps.eq(stepIdx);
276 var curStep = this.steps.eq($this.curStepIdx);
279 if(stepIdx != $this.curStepIdx){
280 if($.isFunction(options.onLeaveStep)) {
281 if(!options.onLeaveStep.call(this,$(curStep))){
286 if (options.updateHeight)
287 elmStepContainer.height($($(selStep, obj).attr("href"), obj).outerHeight());
288 if(options.transitionEffect == 'slide'){
289 $($(curStep, obj).attr("href"), obj).slideUp("fast",function(e){
290 $($(selStep, obj).attr("href"), obj).slideDown("fast");
291 $this.curStepIdx = stepIdx;
292 $this.SetupStep(curStep,selStep);
294 } else if(options.transitionEffect == 'fade'){
295 $($(curStep, obj).attr("href"), obj).fadeOut("fast",function(e){
296 $($(selStep, obj).attr("href"), obj).fadeIn("fast");
297 $this.curStepIdx = stepIdx;
298 $this.SetupStep(curStep,selStep);
300 } else if(options.transitionEffect == 'slideleft'){
302 var curElementLeft = 0;
303 if(stepIdx > $this.curStepIdx){
304 nextElmLeft1 = contentWidth + 10;
306 curElementLeft = 0 - $($(curStep, obj).attr("href"), obj).outerWidth();
308 nextElmLeft1 = 0 - $($(selStep, obj).attr("href"), obj).outerWidth() + 20;
310 curElementLeft = 10 + $($(curStep, obj).attr("href"), obj).outerWidth();
312 if(stepIdx == $this.curStepIdx){
313 nextElmLeft1 = $($(selStep, obj).attr("href"), obj).outerWidth() + 20;
315 curElementLeft = 0 - $($(curStep, obj).attr("href"), obj).outerWidth();
317 $($(curStep, obj).attr("href"), obj).animate({left:curElementLeft},"fast",function(e){
318 $($(curStep, obj).attr("href"), obj).hide();
322 $($(selStep, obj).attr("href"), obj).css("left",nextElmLeft1);
323 $($(selStep, obj).attr("href"), obj).show();
324 $($(selStep, obj).attr("href"), obj).animate({left:nextElmLeft2},"fast",function(e){
325 $this.curStepIdx = stepIdx;
326 $this.SetupStep(curStep,selStep);
329 $($(curStep, obj).attr("href"), obj).hide();
330 $($(selStep, obj).attr("href"), obj).show();
331 $this.curStepIdx = stepIdx;
332 $this.SetupStep(curStep,selStep);
337 this.SetupStep = function(curStep,selStep)
339 $(curStep, obj).removeClass("selected");
340 $(curStep, obj).addClass("done");
342 $(selStep, obj).removeClass("disabled");
343 $(selStep, obj).removeClass("done");
344 $(selStep, obj).addClass("selected");
345 $(selStep, obj).attr("isDone",1);
346 $this.adjustButton();
347 if($.isFunction(options.onShowStep)) {
348 if(!options.onShowStep.call(this,$(selStep))){
354 this.validate_callback = function(validated) {
355 /* In case of failure, inform the user of what went wrong */
360 /* Otherwise, proceed to next step */
361 $this.GoToNextStep();
364 this.doForwardProgress = function()
366 var curStep = this.steps.eq($this.curStepIdx);
367 var step = this.get_step(curStep);
368 var plugin = this.get_plugin(step);
370 /* If the plugin has a validate method, trigger it and wait for
372 if (plugin.validate) {
373 /* Trigger validation code and wait for callback */
374 // XXX We should inform the user about progress and disable buttons
375 plugin.validate(this.validate_callback);
379 /* Otherwise, proceed to next step */
383 this.GoToNextStep = function()
385 var nextStepIdx = $this.curStepIdx + 1;
387 if(this.steps.length <= nextStepIdx){
388 if (!options.cycleSteps) {
393 this.LoadContent(nextStepIdx);
396 this.doBackwardProgress = function()
398 var nextStepIdx = $this.curStepIdx-1;
400 if(!options.cycleSteps){
403 nextStepIdx = this.steps.length - 1;
405 this.LoadContent(nextStepIdx);
408 this.adjustButton = function()
410 if(!options.cycleSteps){
411 if(0 >= $this.curStepIdx){
412 $(btPrevious).addClass("buttonDisabled");
414 $(btPrevious).removeClass("buttonDisabled");
416 if(($this.steps.length-1) <= $this.curStepIdx){
417 $(btNext).addClass("buttonDisabled");
419 $(btNext).removeClass("buttonDisabled");
423 if(!$this.steps.hasClass('disabled') || options.enableFinishButton){
424 $(btFinish).removeClass("buttonDisabled");
426 $(btFinish).addClass("buttonDisabled");
430 this.showMessage = function(msg)
432 $('.content',msgBox).html(msg);
436 this.setError = function(stepnum,iserror)
439 $(this.steps.eq(stepnum-1), obj).addClass('error')
441 $(this.steps.eq(stepnum-1), obj).removeClass("error");
445 this.setDone = function(stepnum,isdone)
448 $(this.steps.eq(stepnum-1), obj).removeClass("selected").removeClass("disabled").addClass('done')
450 $(this.steps.eq(stepnum-1), obj).removeClass("done");
457 // Default Properties and Events
458 $.fn.Wizard.defaults = {
459 selected: 0, // Selected Step, 0 = first step
460 keyNavigation: true, // Enable/Disable key navigation(left and right keys are used if enabled)
461 enableAllSteps: false,
463 transitionEffect: 'fade', // Effect on navigation, none/fade/slide/slideleft
464 contentURL:null, // content url, Enables Ajax content loading
465 contentCache:true, // cache step contents, if false content is fetched always from ajax url
466 cycleSteps: false, // cycle step navigation
467 includeFinishButton: true, // whether to show a Finish button
468 enableFinishButton: false, // make finish button enabled always
469 errorSteps:[], // Array Steps with errors
471 labelPrevious:'Previous',
472 labelFinish:'Finish',
473 onLeaveStep: null, // triggers when leaving a step
474 onShowStep: null, // triggers when showing a step
475 onFinish: null // triggers when Finish button is clicked