plugins: updated query_editor
[myslice.git] / plugins / wizard / static / js / wizard.js
1 /**
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)
5  * License: GPLv3
6  */
7
8 /*
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.
12  */
13 (function($){
14
15     /***************************************************************************
16      * Method calling logic
17      ***************************************************************************/
18
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 );
24         } else {
25             //$.error( 'Method ' +  method + ' does not exist on jQuery.Wizard' );
26             return undefined;
27         }    
28     };
29
30     /***************************************************************************
31      * Public methods
32      ***************************************************************************/
33
34     var methods = {
35
36         /**
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
41          */
42         init : function ( options ) {
43
44             /* Default settings */
45             var options = $.extend({}, $.fn.Wizard.defaults, options);
46
47             return this.each(function() {
48                 var $this = $(this);
49                 var obj = $(wizard, this);
50
51                 /* An object that will hold private variables and methods */
52                 var form = new Wizard(options, obj);
53                 $this.data('Wizard', form);
54
55                 form.init();
56
57 //                // Initialize the smart-wizard third-party jquery plugin
58 //                $(wizard, this).smartWizard({
59 //                    selected    : options.start_step - 1,
60 //                    errorSteps  : [5],
61 //                    onLeaveStep : leaveAStepCallback,
62 ////                  onFinish    : onFinishCallback
63 //                });
64 //
65 //                // XXX Mark some steps as done !
66 //                $(wizard, this).smartWizard('setDone',{stepnum: 1, isdone:true});
67 //
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')
72 //                        return true;
73 //                    return window[func]();
74 //                }
75 //
76 //                function onFinishCallback(){
77 //                    window.location.href('/');
78 //                }
79
80
81             }); // this.each
82         } // init
83
84     };
85
86     /***************************************************************************
87      * Wizard object
88      ***************************************************************************/
89
90     /**
91      * \param options (dict) : a dictionary of options
92      * \param obj (jQuery ref) : handler to the wizard plugin
93     */
94     function Wizard(options, obj) {
95
96         /* save a reference to this */
97         var $this = this;
98
99         /* member variables */
100         this.options = options;
101
102         /* methods */
103
104         /**
105          * \brief get a handle on a wizard step by id
106            \param stepIdx (integer) : step identifier (0-based)
107            \returns jQuery selector
108          */
109         this.get_step = function(stepIdx) { return $('.plugin', $(stepIdx.attr('href'))); }
110
111         this.get_plugin = function(step) { return step.data().plugin; }
112
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;
118             
119             $this.elmActionBar = $('.actionBar',obj);
120             if($this.elmActionBar.length == 0){
121               $this.elmActionBar = $('<div></div>').addClass("actionBar");                
122             }
123
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);                
128             }
129             
130             $('.close',$this.msgBox).click(function() {
131                 $this.msgBox.fadeOut("normal");
132                 return false;
133             });
134         } // this.init1
135
136         this.init = function() {
137             this.init1();
138
139             var allDivs =obj.children('div'); //$("div", obj);                
140             obj.children('ul').addClass("anchor");
141             allDivs.addClass("content");
142             // Create Elements
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");
149             
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);
154               });
155             }
156
157
158             elmStepContainer.append(allDivs);
159             elmActionBar.append(loader);
160             obj.append(elmStepContainer);
161             obj.append(elmActionBar); 
162             if (options.includeFinishButton) {
163               elmActionBar.append(btFinish);
164             }
165             elmActionBar.append(btNext).append(btPrevious); 
166             contentWidth = elmStepContainer.width();
167
168             $(btNext).click(function() {
169                 if($(this).hasClass('buttonDisabled')){
170                   return false;
171                 }
172                 $this.doForwardProgress();
173                 return false;
174             }); 
175             $(btPrevious).click(function() {
176                 if($(this).hasClass('buttonDisabled')){
177                   return false;
178                 }
179                 $this.doBackwardProgress();
180                 return false;
181             }); 
182             $(btFinish).click(function() {
183                 if(!$(this).hasClass('buttonDisabled')){
184                    if($.isFunction(options.onFinish)) {
185                       if(!options.onFinish.call(this,$(this.steps))){
186                         return false;
187                       }
188                    }else{
189                      var frm = $(obj).parents('form');
190                      if(frm && frm.length){
191                        frm.submit();
192                      }                         
193                    }                      
194                 }
195
196                 return false;
197             }); 
198             
199             $(this.steps).bind("click", function(e){
200                 if(steps.index(this) == $this.curStepIdx){
201                   return false;                    
202                 }
203                 var nextStepIdx = steps.index(this);
204                 var isDone = steps.eq(nextStepIdx).attr("isDone") - 0;
205                 if(isDone == 1){
206                   $this.LoadContent(nextStepIdx);                    
207                 }
208                 return false;
209             }); 
210             
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();
218                     }
219                 });
220             }
221             //  Prepare the steps
222             this.prepareSteps();
223             // Show the first slected step
224             this.LoadContent($this.curStepIdx);                  
225         }
226
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);                 
231             } else {
232                 $(this.steps, obj).removeClass("selected").removeClass("disabled").addClass("done"); 
233                 $(this.steps, obj).attr("isDone",1); 
234             }
235
236             $(this.steps, obj).each(function(i){
237                 $($(this).attr("href"), obj).hide();
238                 $(this).attr("rel",i+1);
239             });
240         }
241                     
242         this.LoadContent = function(stepIdx) {
243             var selStep = this.steps.eq(stepIdx);
244             var ajaxurl = options.contentURL;
245             var hasContent =  selStep.data('hasContent');
246             stepNum = stepIdx+1;
247             if(ajaxurl && ajaxurl.length>0){
248                if(options.contentCache && hasContent){
249                    this.showStep(stepIdx);                          
250                }else{
251                    $.ajax({
252                     url: ajaxurl,
253                     type: "POST",
254                     data: ({step_number : stepNum}),
255                     dataType: "text",
256                     beforeSend: function(){ loader.show(); },
257                     error: function(){loader.hide();},
258                     success: function(res){ 
259                       loader.hide();       
260                       if(res && res.length>0){  
261                          selStep.data('hasContent',true);            
262                          $($(selStep, obj).attr("href"), obj).html(res);
263                          this.showStep(stepIdx);
264                       }
265                     }
266                   }); 
267               }
268             }else{
269               this.showStep(stepIdx);
270             }
271         } // this.LoadContent
272                     
273         this.showStep = function(stepIdx) 
274         {
275             var selStep = this.steps.eq(stepIdx); 
276             var curStep = this.steps.eq($this.curStepIdx);
277
278
279             if(stepIdx != $this.curStepIdx){
280               if($.isFunction(options.onLeaveStep)) {
281                 if(!options.onLeaveStep.call(this,$(curStep))){
282                   return false;
283                 }
284               }
285             }     
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);
293                   });
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);                           
299                   });                    
300             } else if(options.transitionEffect == 'slideleft'){
301                 var nextElmLeft = 0;
302                 var curElementLeft = 0;
303                 if(stepIdx > $this.curStepIdx){
304                     nextElmLeft1 = contentWidth + 10;
305                     nextElmLeft2 = 0;
306                     curElementLeft = 0 - $($(curStep, obj).attr("href"), obj).outerWidth();
307                 } else {
308                     nextElmLeft1 = 0 - $($(selStep, obj).attr("href"), obj).outerWidth() + 20;
309                     nextElmLeft2 = 0;
310                     curElementLeft = 10 + $($(curStep, obj).attr("href"), obj).outerWidth();
311                 }
312                 if(stepIdx == $this.curStepIdx){
313                     nextElmLeft1 = $($(selStep, obj).attr("href"), obj).outerWidth() + 20;
314                     nextElmLeft2 = 0;
315                     curElementLeft = 0 - $($(curStep, obj).attr("href"), obj).outerWidth();                           
316                 }else{
317                     $($(curStep, obj).attr("href"), obj).animate({left:curElementLeft},"fast",function(e){
318                       $($(curStep, obj).attr("href"), obj).hide();
319                     });                       
320                 }
321
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);                      
327                 });
328             } else{
329                 $($(curStep, obj).attr("href"), obj).hide(); 
330                 $($(selStep, obj).attr("href"), obj).show();
331                 $this.curStepIdx =  stepIdx;                        
332                 $this.SetupStep(curStep,selStep);
333             }
334             return true;
335         } // this.showStep
336                     
337         this.SetupStep = function(curStep,selStep)
338         {
339             $(curStep, obj).removeClass("selected");
340             $(curStep, obj).addClass("done");
341             
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))){
349                  return false;
350                }
351             } 
352         }                
353
354         this.validate_callback = function(validated) {
355             /* In case of failure, inform the user of what went wrong */
356             if (!validated) {
357                 return;
358             }
359             
360             /* Otherwise, proceed to next step */
361             $this.GoToNextStep();
362         }
363     
364         this.doForwardProgress = function()
365         {
366             var curStep = this.steps.eq($this.curStepIdx);
367             var step = this.get_step(curStep);
368             var plugin = this.get_plugin(step);
369
370             /* If the plugin has a validate method, trigger it and wait for
371              * callback */
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);
376                 return;
377             }
378
379             /* Otherwise, proceed to next step */
380             this.GoToNextStep();
381         }
382         
383         this.GoToNextStep = function()
384         {
385             var nextStepIdx = $this.curStepIdx + 1;
386
387             if(this.steps.length <= nextStepIdx){
388                 if (!options.cycleSteps) {
389                     return false;
390                 }                  
391                 nextStepIdx = 0;
392             }
393             this.LoadContent(nextStepIdx);
394         }
395                     
396         this.doBackwardProgress = function()
397         {
398             var nextStepIdx = $this.curStepIdx-1;
399             if(0 > nextStepIdx){
400               if(!options.cycleSteps){
401                 return false;
402               }
403               nextStepIdx = this.steps.length - 1;
404             }
405             this.LoadContent(nextStepIdx);
406         }  
407                     
408         this.adjustButton = function()
409         {
410             if(!options.cycleSteps){                
411               if(0 >= $this.curStepIdx){
412                 $(btPrevious).addClass("buttonDisabled");
413               }else{
414                 $(btPrevious).removeClass("buttonDisabled");
415               }
416               if(($this.steps.length-1) <= $this.curStepIdx){
417                 $(btNext).addClass("buttonDisabled");
418               }else{
419                 $(btNext).removeClass("buttonDisabled");
420               }
421             }
422             // Finish Button 
423             if(!$this.steps.hasClass('disabled') || options.enableFinishButton){
424               $(btFinish).removeClass("buttonDisabled");
425             }else{
426               $(btFinish).addClass("buttonDisabled");
427             }                  
428         }
429                     
430         this.showMessage = function(msg)
431         {
432             $('.content',msgBox).html(msg);
433             msgBox.show();
434         }
435                     
436         this.setError = function(stepnum,iserror)
437         {
438             if(iserror){                    
439               $(this.steps.eq(stepnum-1), obj).addClass('error')
440             }else{
441               $(this.steps.eq(stepnum-1), obj).removeClass("error");
442             }                                   
443         }                        
444
445         this.setDone = function(stepnum,isdone)
446         {
447             if(isdone){                    
448               $(this.steps.eq(stepnum-1), obj).removeClass("selected").removeClass("disabled").addClass('done')
449             }else{
450               $(this.steps.eq(stepnum-1), obj).removeClass("done");
451             }                                   
452         }
453     }
454
455     /// KEEP BELOW
456
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,
462         updateHeight: true,
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
470         labelNext:'Next',
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
476     };    
477
478 })( jQuery );