source: Dev/trunk/js/sequencerScripts.js @ 188

Last change on this file since 188 was 188, checked in by fpvanagthoven, 13 years ago
File size: 35.4 KB
Line 
1/*
2 * You can change the type of sequencer by including a hidden input field with id "contentTypeField" in your document.
3 * The initEditor() method will then adjust internal variables in the sequencer object to match that type of content.
4 *
5 * Note: code makes heavy use of shorthand functions:
6 * ce(var) = document.createElement(var);
7 * ge(var) = document.getElementById(var);
8 * Defined in generalscripts.js
9 */
10
11var sequencer = createVarArray();
12
13// Basic functions, intialization, updating
14
15function createVarArray(){
16    // Function that returns creates the global variable object, the sequencer's variable storage.
17   
18    if (sequencer) delete window.sequencer;
19    return {   // GLOBAL VAR TO STORE SEQUENCER SETTINGS!
20        uid: "",                // The unique id of this sequencer (not DB related!). This will help to avoid global var conflicts. Assign this randomly! (STRING)
21        session: {              // Properties of the currently loaded session
22            title: "",              // Title or name (STRING)
23            uid: "",                // Database UID of the current session (STRING)
24            pipeline: {             // Pipeline
25                uids: [],               // Uids of objects in pipeline (STRING)
26                types: [],              // Types of objects in pipeline (STRING)
27                upToDate: []            // Whether or not object displays are up to date (BOOL)
28            }
29        },
30        survey: {               // Properties of the loaded survey, if applicable (either this or the session tree above is filled in!) [THIS IS NOT CURRENTLY USED AND I SHOULD DELIBERATE ON WHAT VARIABLES TO PUT IN THIS TREE TO MAKE A FUNCTIONAL EDITOR THAT ALSO MATCHES THE DB FORMAT!]
31            title: "",              // Title or the name of the survey (STRING)
32            uid: "",                // Uid of the survey (STRING)
33            description: "",        // Description of the survey (STRING)
34            questions: {            // Properties of the questions contained within this survey
35                uids: [],               // An array of uids of the questions, in the order that they appear in the survey (STRING)
36                upToDate: []            // Whether or not a certain question needs an update (BOOL)
37            }
38        },
39        state: {                // Operating state of the sequencer
40            editing: false,         // Whether or not one of the contained child objects is currently being edited or in edit mode. Which one can be determined from the selectedObject property. (BOOL)
41            updating: false,        // Whether or not new steps are currently being queried (to disable any further actions) (BOOL)
42            numSteps: 0,            // Number of objects currently drawn in the editor (not necessarily same as number of objects in pipeline/survey!) (INTEGER)
43            loaded: false,          // Whether or not the sequencer content has been updated for the first time (BOOL)
44            selectedObject: {       // Properties of the currently selected step
45                uid: "",                // UID of this step (STRING)
46                index: null             // The 'index' of this step in the current sequencer view (NOT the pipeline!) (INTEGER)
47            },
48            pages: {                // State regarding page division of content objects
49                total: null,            // The number of pages the content is divided across
50                currentPage: null       // The page that is currently displayed in the sequencer           
51            }
52        },
53        settings: {             // Various settings to determine the workings of the sequencer
54            content: {              // Properties related to the content view of the sequencer
55                contentType: null,      // Type of the loaded parent object (STRING)
56                width: null,            // Width of the viewing area (INTEGER)
57                height: null,           // Height of the viewing area (INTEGER)
58                maxObjects: null,       // The maximum number of content elements to be displayed at once time (INTEGER)
59                orientation: null,      // Whether the editor should be a vertical or horizontal editor (STRING)
60                pages: false            // Whether or not to divide content across pages
61            },
62            efficientUpdating: true // Whether or not to use selective querying of the database for new step objects. True will only refresh out-of-date steps, False will refresh the whole pipeline (BOOL)
63        }
64    };
65
66}
67
68function loadSequencer() {
69    // Reads hidden fields created by PHP, copies the values into the global var then deletes them.
70   
71    // Load hidden fields and set required properties in global object var.
72    try {
73        // settings fields first
74        initSequencer();
75       
76        switch (sequencer.settings.content.contentType.toLowerCase()) {
77            case "session":
78                // Content-related fields next
79                var fPipelineString = ge("pipelineStringField");
80                var fPipelineTypes = ge("pipelineTypeField");
81                var fSessionUid = ge("sessionField");
82                var fSessionTitle = ge("sessionTitleField");
83                sequencer.session.title = fSessionTitle.value;
84                sequencer.session.uid = fSessionUid.value;
85                sequencer.session.pipeline.uids = stringToArray(fPipelineString.value, ",");
86                sequencer.session.pipeline.types = stringToArray(fPipelineTypes.value, ",");
87                sequencer.session.pipeline.upToDate = new Array();
88       
89                for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) {
90                    sequencer.session.pipeline.upToDate.push(true);
91                }
92                break;
93            case "survey":
94                var fSurveyUid = ge("surveyUidField");
95                var fQuestionUids = ge("questionUidsField");
96                var fSurveyTitle = ge("surveyTitleField");
97                var fnumQuestions = ge("numQuestionsField");
98                var fSurveyDescription = ge("surveyDescriptionField");
99                sequencer.survey.title = fSurveyTitle.value;
100                sequencer.survey.uid = fSurveyUid.value;
101                sequencer.survey.description = fSurveyDescription.value;
102                sequencer.survey.questions.uids = stringToArray(fQuestionUids.value, ",");
103                break;
104            default:
105                break;
106        }
107       
108       
109        sequencer.state.numSteps = 0;
110        sequencer.state.loaded = false;
111        sequencer.settings.efficientUpdating = true;
112    }
113    catch (e) {
114        // Alert developer of any errors while setting these variables
115        for (error in e) alert(error.message);
116    }
117    // Then remove the hidden fields from the HTML document
118    var hiddenInputs = ge("hiddenInputs");
119    hiddenInputs.parentNode.removeChild(hiddenInputs);
120   
121    // finally, run updateSequencer to refresh the visual display of the pipeline
122    updateSequencer();
123}
124
125function initSequencer() {
126    // Called from loadSequencer(). Sets sequencer.settings properties depending on content type and values passed by PHP in hidden fields (currently only one), then removes these fields.
127    // Example: stuff like editor orientation, width/height, content type, maxObjects contained, etc...
128   
129    // load settings fields first
130    var fContentType = ge("sContentTypeField");
131    var content = ge("seqContent");
132    //sequencer.settings.content.contentType = fContentType.value.toLowerCase();
133    sequencer.settings.content.contentType = fContentType.value;
134    //Then select settings from a few presets
135   
136    switch (sequencer.settings.content.contentType.toLowerCase()) {
137        case "session":
138            sequencer.settings.content.orientation = "horizontal";
139            sequencer.settings.content.width = 800;
140            sequencer.settings.content.height = 125;
141            content.style.width = sequencer.settings.content.width+"px";
142            content.style.height = sequencer.settings.content.height+"px";
143            addClass(content, "horizontal");
144            break;
145        case "survey":
146            sequencer.settings.content.orientation = "vertical";
147            sequencer.settings.content.width = 650;     //guesstimated
148            var roomH = screen.availHeight;
149            sequencer.settings.content.height = roomH - 200;
150            content.style.width = sequencer.settings.content.width+"px";
151            content.style.height = sequencer.settings.content.height+"px";
152            addClass(content, "vertical");
153            break;
154        default:
155            break;
156    }
157    fContentType.parentNode.parentNode.removeChild(fContentType.parentNode);
158}
159
160// Updating, drawing, formatting
161
162function updateSequencer() {
163    // Code that manages drawing and adding of new visual representations of objects in the sequencer.
164
165    /*
166         * Description:
167         * This function updates the visual elements in the sequencer content view to match the current state of the sequencer.session.pipeline property.
168         * It queries the database for object properties via AJAX (returnStep/Display/.php), then inserts divider div's in between where needed.
169         */
170       
171    switch (sequencer.settings.content.contentType.toLowerCase()) {
172        case "session":
173            updateSequencer_Session();
174            break;
175        case "survey":
176            updateSequencer_Survey();
177            break;
178        default:
179            // Why would this even be called?
180            break;
181    }
182}
183
184function updateSequencer_Session() {
185    var content = ge("seqContentWrapper");
186    var requestString, needsUpdating;
187    var args;
188   
189    if (sequencer.state.loaded == false || sequencer.settings.efficientUpdating == false) {  // This is the first update of the sequencer since page load, therefore it contains no previous steps
190        // Stop running this function if the pipeline does not contain any elements to draw
191        if (sequencer.session.pipeline.uids.length == 0 || !sequencer.session.pipeline.uids.length) return;
192        // First clear the entire content wrapper, just for safety and in case efficientUpdating is off
193        while (content.firstChild) {
194            content.removeChild(content.firstChild);
195        }
196        args = [];
197        needsUpdating = [];
198        for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) {
199            args.push({
200                uid: sequencer.session.pipeline.uids[i],
201                type: sequencer.session.pipeline.types[i]
202            });
203            needsUpdating.push(new Array(i, sequencer.session.pipeline.uids[i], sequencer.session.pipeline.types[i]));
204        }
205       
206        requestString = "args="+JSON.stringify(args);
207        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
208            content.removeChild(loadingGif);
209            insertNewObjects(result.responseText, needsUpdating);
210        }, true);
211        sequencer.state.loaded = true;
212        var loadingGif = ce("div");
213        loadingGif.innerHTML = "<img src='images/ui/ajax-loader-round.gif' style='float: left; margin:auto auto;' />";
214        content.appendChild(loadingGif);
215    }
216    else {
217        // This means that one or more steps are being added, not an entire pipeline's worth of them
218        needsUpdating = new Array();
219        args = [];
220        // Add steps that need updating to the needsUpdating array (index, uid, type).
221        for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) {
222            if (sequencer.session.pipeline.upToDate[i] == true) continue;
223            needsUpdating.push(new Array(i, sequencer.session.pipeline.uids[i], sequencer.session.pipeline.types[i]));
224            args.push({
225                uid: sequencer.session.pipeline.uids[i],
226                type: sequencer.session.pipeline.types[i]
227            });       
228        }
229       
230        requestString = "args="+JSON.stringify(args);
231        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
232            insertNewObjects(result.responseText, needsUpdating);
233        }, true);
234       
235        // Optional bit with the loading GIF
236        for (var i = 0; i < needsUpdating.length; i++) {
237            var loadingDiv = ce("div");
238            loadingDiv.className = "displayStep loading";
239            loadingDiv.innerHTML = "<img src='images/ui/ajax-loader-round.gif' />";
240            if (needsUpdating[i][0] > sequencer.state.numSteps-1) {
241                content.appendChild(loadingDiv);
242                sequencer.state.numSteps++;
243            }
244            else {
245                content.replaceChild(loadingDiv, content.childNodes[i][0]*2);
246            }
247        }
248        updateDividers();
249    // End optional
250    }
251}
252
253// THIS NEEDS SOME WORK! A LOT OF IT ACTUALLY!
254function updateSequencer_Survey() {
255   
256    var content = ge("seqContentWrapper");
257    var requestString, needsUpdating, args;
258    // Create a reference to the correct field, this might not be needed if we do not generalize the updateSequencer function and instead keep two separate update functions for separate content types.
259    var variables = sequencer.survey;
260   
261    if (sequencer.state.loaded == false || sequencer.settings.efficientUpdating == false) {  // This is the first update of the sequencer since page load, therefore it contains no previous steps
262        // Stop running this function if the questions array does not contain any elements to draw
263        if (sequencer.survey.questions.uids.length == 0 || !sequencer.survey.questions.uids.length) return;
264        // First clear the entire content wrapper, just for safety and in case efficientUpdating is off
265        while (content.firstChild) {
266            content.removeChild(content.firstChild);
267        }
268        args = [];
269        needsUpdating = [];
270        for (var i = 0; i < sequencer.survey.questions.uids.length; i++) {
271            args.push({
272                uid: sequencer.survey.questions.uids[i],
273                type: "Question"
274            });
275            needsUpdating.push(new Array(i, sequencer.survey.questions.uids[i], "Question"));
276        }
277       
278        requestString = "args="+JSON.stringify(args);
279        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
280            content.removeChild(loadingGif);
281            insertNewObjects(result.responseText, needsUpdating);
282            sequencer.state.loaded = true;
283        }, true);
284        sequencer.state.loaded = false;
285        var loadingGif = ce("div");
286        loadingGif.innerHTML = "<img src='images/ui/ajax-loader-round.gif' style='float: left; margin:auto auto;' />";
287        content.appendChild(loadingGif);
288    }
289    else {
290
291        // This means that one or more steps are being added, not an entire pipeline's worth of them
292        debugger;
293        needsUpdating = new Array();
294        args = [];
295        // Add steps that need updating to the needsUpdating array (index, uid, type).
296        for (var i = 0; i < sequencer.survey.questions.uids.length; i++) {
297            if (sequencer.survey.questions.upToDate[i] == true) continue;
298            needsUpdating.push(new Array(i, sequencer.survey.questions.uids[i], "Question"));
299            args.push({
300                uid: sequencer.survey.questions.uids[i],
301                type: "Question"
302            });       
303        }
304       
305        requestString = "args="+JSON.stringify(args);
306        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
307            insertNewObjects(result.responseText, needsUpdating);
308        }, true);
309       
310        // Optional bit with the loading GIF
311        for (var i = 0; i < needsUpdating.length; i++) {
312            var loadingDiv = ce("div");
313            loadingDiv.className = "displayStep loading";
314            loadingDiv.innerHTML = "<img src='images/ui/ajax-loader-round.gif' />";
315            if (needsUpdating[i][0] > sequencer.state.numSteps-1) {
316                content.appendChild(loadingDiv);
317                sequencer.state.numSteps++;
318            }
319            else {
320                content.replaceChild(loadingDiv, content.childNodes[i][0]*2);
321            }
322        }
323        updateDividers();
324    // End optional
325       
326    }
327       
328}
329
330function updateDividers() {
331    // Function that checks for correct number of dividers in the pipeline, adds or deletes as needed.
332   
333    var content = ge("seqContentWrapper");
334    // Loop through all elements in seqContentWrapper
335    for (var i = 0; i < content.childNodes.length; i++) {
336        var element = content.childNodes[i];
337        // If the element is not a displayStep, continue with the next childNode.
338        if (!hasClass(element, "displayStep")) {
339            continue;
340        }
341        // Get the two elements next to the current element, if they don't exist or are not displaySteps, store false.
342        var lastElement = (element.previousSibling && element.previousSibling.nodeName == "DIV") ? element.previousSibling : false;
343        var nextElement = (element.nextSibling && element.nextSibling.nodeName == "DIV") ? element.nextSibling : false;
344        // If these elements exist, and they are not dividers, add an element in between.
345        if (lastElement != false) {
346            if (!hasClass(lastElement, "divider")){
347                var newDivider = ce("div");
348                addClass(newDivider, "divider");
349                addClass(newDivider, sequencer.settings.content.orientation);
350                content.insertBefore(newDivider, element);
351                delete newDivider;
352            }
353        }
354        if (nextElement != false) {
355            if (!hasClass(nextElement, "divider")){
356                var newDivider = ce("div");
357                addClass(newDivider, "divider");
358                addClass(newDivider, sequencer.settings.content.orientation);
359                content.insertBefore(newDivider, nextElement);
360                delete newDivider;
361            }
362        }
363    }
364   
365    // Because updateDividers is always called last, we set updating to false here, so the user can again issue commands to the pipeline
366    sequencer.state.updating = false;   // Re-enable new actions
367}
368
369// Adding new objects
370
371function submitToolbox(type) {
372    // Handles new object creation code when user clicks on a toolbox button
373   
374    // Do not accept new creation requests if the sequencer is still updating a previous object.
375    if (sequencer.state.updating == true) return;
376    sequencer.state.updating = true;
377    deselectStep();   
378    var c = "objectToCreate="+type;
379    var u = "createObject.php";
380       
381    newAjaxRequest(c, u, function(result) {
382        sequencer.session.pipeline.uids.push(removeNL(result.responseText));
383        sequencer.session.pipeline.types.push(type);
384        sequencer.session.pipeline.upToDate.push(false);
385        updateSequencer();
386    }, true);
387}
388
389function insertNewObjects(responseText, needsUpdating) {
390    // Container function that calls different insertNewX() functions depending on content type. Called from updateSequencer().
391    var response = JSON.parse(responseText);
392    // For now I assume that only one type of element can be displayed in the editor at one time. Therefore, the type of response[0] is the type of all elements of response.
393    switch (sequencer.settings.content.contentType.toLowerCase()) {
394        case "session":
395            insertNewSteps(response, needsUpdating);
396            break;
397        case "survey":
398            insertNewQuestions(response, needsUpdating);
399            break;
400        default:
401            break;
402               
403    }
404}
405
406function addQuestion_Click() {
407    if (sequencer.state.updating == true) return;
408    sequencer.state.updating = true;
409    deselectStep();   
410    var c = "objectToCreate=question";
411    var u = "createObject.php";
412       
413    newAjaxRequest(c, u, function(result) {
414        sequencer.survey.questions.uids.push(removeNL(result.responseText));
415        sequencer.survey.questions.upToDate.push(false);
416        updateSequencer();
417    }, true);
418}
419
420//> Session specific
421
422function insertNewSteps(response, needsUpdating) {
423    /*
424         * This is a function displaying how to handle the visual object representation in solely javascript.
425         * Communication of relevant variables between PHP and JS happens in JSON format.
426         * PHP returns a JSON array of objects to be created by JS
427         * JS then loops through this array and creates DIVS to be inserted into the sequencer.
428         * These are inserted at the position needsUpdating gives us.
429         */
430    var content = ge("seqContentWrapper");
431    // Remove optional loading images
432    for (var  i = 0; i < content.childNodes.length; i++) {
433        if (hasClass(content.childNodes[i], "loading")) {
434            content.removeChild(content.childNodes[i]);
435        }
436    }
437    // End optional
438   
439    for (var i = 0; i < response.length; i++) {
440        var tempDiv = ce("div");
441        tempDiv.id = response[i].uid;
442        tempDiv.className = "displayStep";
443        var divImageContainer = ce("div");
444        divImageContainer.className = "displayStepIcon";
445        divImageContainer.addEventListener("click", function(){
446            clickStep(this.parentNode.id);
447        }, false);
448        var divImage = ce("img");
449        divImage.src = "images/icons/"+response[i].type.toLowerCase()+".png";
450        divImageContainer.appendChild(divImage);
451        tempDiv.appendChild(divImageContainer);
452        var divLabel = ce("p");
453        divLabel.innerHTML = response[i].title;
454        tempDiv.appendChild(divLabel);
455       
456        // This for needs to loop backwards so that the steps at the end of the pipeline are added or changed first. This keeps the childNodes index for all further steps intact.
457        for (var j = needsUpdating.length-1; j >= 0; j--) {
458            if (needsUpdating[j][1] != response[i].uid) continue;
459            if (needsUpdating[j][0] > sequencer.state.numSteps-1) {
460                content.appendChild(tempDiv);
461            }
462            else {
463                content.replaceChild(tempDiv, content.childNodes[j][0]*2);
464            }
465            sequencer.session.pipeline.upToDate[needsUpdating[j][0]] = true;
466        }
467    }
468   
469    updateDividers();
470}
471
472function deleteStep() {
473    // Delete a step from both the pipeline.uids variable and its visual representation from the sequencer. Does not actually delete object from database!
474   
475    // check if a step is selected
476    if (sequencer.state.selectedObject.uid == null) {
477        return;
478    }
479    var uid = sequencer.state.selectedObject.uid;
480    // deselect the step to reset the info panel and selection code
481    deselectStep();
482    // splice the step's data from the pipeline
483    var index = sequencer.session.pipeline.uids.indexOf(uid);
484    if (index >= 0 && index < sequencer.session.pipeline.uids.length) {
485        sequencer.session.pipeline.uids.splice(index, 1);
486        sequencer.session.pipeline.types.splice(index, 1);
487        sequencer.session.pipeline.upToDate.splice(index, 1);
488        // Then delete the step visually
489        var element = ge(uid);
490        var divider;
491        if (!element.nextSibling) {
492            // the element is at the end of the pipeline
493            // therefore we remove the previous divider.
494            // Note: it could also be the only element left in the pipeline!
495            divider = (element.previousSibling) ? element.previousSibling : false;
496            if (divider != false) {
497                divider.parentNode.removeChild(divider);
498            }
499        }
500        else {
501            // the element is at any position except the last, therefore we remove the next divider
502            divider = (element.nextSibling) ? element.nextSibling : false;
503            if (divider != false) {
504                divider.parentNode.removeChild(divider);
505            }
506        }
507       
508        // Finally, remove the element itself.
509        element.parentNode.removeChild(element);
510        sequencer.state.numSteps--;
511       
512    }
513}
514
515//> Survey specific
516
517function insertNewQuestions(response, needsUpdating) {
518    //Code that inserts or replaces new object displays in the sequencer. Question version.
519    var content = ge("seqContentWrapper");
520    // Loop through returned question objects
521    for (var i = 0; i < response.length; i++) {
522        // Define the outer frame
523        var frameDiv = ce("div");
524        frameDiv.className = "smallFrame question";
525        frameDiv.id = response[i].uid;
526        var titleDiv = ce("div");
527        titleDiv.className = "smallTitle";
528        var numberDiv = ce("div");
529        numberDiv.className = "listNumber";
530        numberDiv.innerHTML = i.toString();
531        titleDiv.appendChild(numberDiv);
532        titleDiv.innerHTML += response[i].uid;
533        frameDiv.appendChild(titleDiv);
534       
535        // The frame now has a header bar
536        // On to the content frame
537        // Will use new "ce();" function, shorthand for ce
538       
539        var contentDiv = ce("div");
540        contentDiv.className = "content";
541        var questionBody = ce("p");
542        questionBody.innerHTML = response[i].description;
543        var questionParamsDiv = ce("div");
544        questionParamsDiv.className = "questionParamsView";
545        questionParamsDiv.innerHTML = "Object type: "+response[i].type;
546        contentDiv.appendChild(questionBody);
547        contentDiv.appendChild(questionParamsDiv);
548        frameDiv.appendChild(contentDiv);
549       
550        // And finally the controls div
551        var controlsDiv = ce("div");
552        controlsDiv.className = "controls";
553        var editButton = ce("input");
554        var removeButton = ce("input");
555        editButton.value = "Edit";
556        removeButton.value = "Remove";
557        editButton.className = "smallButton";
558        removeButton.className = "smallButton";
559        editButton.addEventListener("click", function(e){
560            alert('Editing not yet supported!');
561        }, false);
562        removeButton.addEventListener("click", function(e){
563            alert('Removing not yet supported!');
564        }, false);
565        controlsDiv.appendChild(editButton);
566        controlsDiv.appendChild(removeButton);
567        frameDiv.addEventListener("click", function(){
568            clickStep(this.id);
569        }, false);
570        frameDiv.appendChild(controlsDiv);
571       
572        // We now have a full question display DIV contained in the frameDiv variable. We should now add this to the sequencer content.
573        debugger;
574        for (var j = needsUpdating.length - 1; j >= 0; j--) {
575            if (needsUpdating[j][1] != response[i].uid) continue;
576            if (needsUpdating[j][0] > sequencer.state.numSteps-1) {
577                content.appendChild(frameDiv);
578                sequencer.state.numSteps++;
579            }
580            else {
581                content.replaceChild(frameDiv, content.childNodes[needsUpdating[j][0]*2]);
582            }
583            sequencer.survey.questions.upToDate[needsUpdating[j][0]] = true;
584           
585        }
586    }
587   
588    sequencer.state.updating = false;   //re-enable user commands
589}
590
591// general functions and user actions
592
593function clickStep(uid) {
594    // Handles selection of steps
595   
596    if (uid == sequencer.state.selectedObject.uid) {  // user deselected a currently selected step.
597        deselectStep();
598    }
599    else {
600        if (sequencer.state.selectedObject.uid != undefined && sequencer.state.selectedObject.uid != "") {
601            // Change selection if something is already selected
602            deselectStep();
603            selectStep(uid);
604        }
605        else {
606            // Make new selection if nothing was selected
607            selectStep(uid);
608        }       
609    }
610}
611
612function selectStep(uid) {
613    // Called from clickStep(), manages CSS class assignment and updating of state variables. Also calls for info panel update.
614   
615    var element = ge(uid);
616    if (element) {
617        addClass(element, "selected");
618        var type;
619        switch (sequencer.settings.content.contentType.toLowerCase()) {
620            case "session":
621                type = sequencer.session.pipeline.types[sequencer.session.pipeline.uids.indexOf(uid)];
622                break;
623            case "survey":
624                type = "Question";
625                break;
626            default:
627                //Dunno
628                break;
629        }
630        (type != "survey") ? ajaxInfoRequest(uid, ge("infoPanelContent"), type) : type=type /*This does nothing*/;
631        sequencer.state.selectedObject.uid = uid;
632        sequencer.state.selectedObject.index = null;  //Don't know how to do this yet.
633    }
634}
635
636function deselectStep() {
637    // Called from clickStep(). Handles unassignment and updating of state variables. Clears info panel.
638   
639    if (!sequencer.state.selectedObject.uid) return;
640    var element = ge(sequencer.state.selectedObject.uid);
641    removeClass(element, "selected");
642    sequencer.state.selectedObject.uid = null;
643    sequencer.state.selectedObject.index = null;
644    var infoPanel = ge("infoPanelContent");
645    if (infoPanel) {
646        while (infoPanel.firstChild) infoPanel.removeChild(infoPanel.firstChild);
647    }
648}
649
650function ajaxInfoRequest(uid, el, type) {
651    // Info panel update.
652   
653    var c = "uid="+uid;
654    c += "&type="+type;
655    var u = "getInfo.php";
656    newAjaxRequest(c, u, function(result) {
657        el.innerHTML = result.responseText;
658    }, true);
659}
660
661function savePipeline (confirmSave) {
662    // Sends an AJAX request to the PHP server that saves the current pipeline. Does not yet support surveys.
663   
664    // First check if user should confirm save action or not.
665    var answer;
666    if (confirmSave == true) {
667        answer = confirm("Save changes to pipeline?");
668    }
669    else {
670        answer = true;
671    }
672    if (answer == false) return;
673    // Then compose requestString for savesession.php, containing pipeline uids, types and the session uid.
674    // TODO: should eventually include stuff like session name as well!
675    var requestString = "uids=";
676    requestString += arrayToString(sequencer.session.pipeline.uids, ",");
677    requestString = requestString.slice(0, requestString.length - 1);   // remove trailing commas
678    requestString += "&types=";
679    requestString += arrayToString(sequencer.session.pipeline.types, ",");
680    requestString = requestString.slice(0, requestString.length - 1);   // remove trailing commas
681    requestString += "&sessionUid=";
682    requestString += sequencer.session.uid;
683    console.log(requestString);
684    newAjaxRequest(requestString, "savesession.php", function(result){
685        console.log(result.responseText);
686    }, true);
687}
688
689function editStep() {
690    // Redirects the browser to the appropriate editor for the selected step type.
691   
692    // first save
693    savePipeline(false);
694    // Then post relevant information so the next editor page knows what object it is supposed to be editing.
695    var postForm = ce("form");
696    var type = sequencer.session.pipeline.types[sequencer.session.pipeline.uids.indexOf(sequencer.state.selectedObject.uid)];
697    postForm.action = type.toLowerCase()+"Editor.php";
698    postForm.method = "POST";
699    var objectUid = ce("input");
700    objectUid.type = "hidden";
701    objectUid.name = "objectUid";
702    objectUid.value = sequencer.state.selectedObject.uid;
703    postForm.appendChild(objectUid);
704    postForm.submit();
705}
706
707function moveStep (direction) {
708    // Moves the selected step up/down (left/right) in the pipeline.
709   
710    // Check if a step is selected
711    if (sequencer.state.selectedObject.uid == null || direction == null) return;
712    // Check if the step is not at either end of the pipeline
713    var index = sequencer.session.pipeline.uids.indexOf(sequencer.state.selectedObject.uid);
714    if (index == -1) return;
715    if ((index < 0) || (index >= sequencer.session.pipeline.uids.length) || (index == 0 && direction == -1) || (index == sequencer.session.pipeline.uids.length - 1 && direction == 1)) {
716        alert("Cannot move out of bounds!");
717        return;
718    }
719   
720    // Find the two elements in the editor content display
721    var content = ge("seqContentWrapper");
722    var element = ge(sequencer.session.pipeline.uids[index]);
723    var otherElement = ge(sequencer.session.pipeline.uids[index+direction]);
724    // First buffer the two elements
725    var tempElement = element.cloneNode(true);
726    var tempOtherElement = otherElement.cloneNode(true);
727    var placeHolderElement = ce("div");
728    placeHolderElement.id = "placeholder_element";
729    content.replaceChild(placeHolderElement, otherElement);
730    content.replaceChild(tempOtherElement, element);
731    content.replaceChild(tempElement, placeHolderElement);
732    //This should work.
733    // A-B     Start positions, backup to tA and tB
734    // A-X     Replace B with placeholder X
735    // B-X     Replace A with tB
736    // B-A     Replace placeholder X with tA.
737    // The two elements are now swapped.
738     
739    // Now swap the array entries.
740    sequencer.session.pipeline.uids[index] = sequencer.session.pipeline.uids.splice(index+direction, 1, sequencer.session.pipeline.uids[index])[0];
741    sequencer.session.pipeline.types[index] = sequencer.session.pipeline.types.splice(index+direction, 1, sequencer.session.pipeline.types[index])[0];
742   
743    // Finally, rebind the onCLick events to work properly again. I don't know if this 'clones' the event as well (therefore re-adding them casuses memory bloat...) or not.
744    for (var i = 0; i < tempOtherElement.childNodes.length; i++){
745        var childNode = tempOtherElement.childNodes[i];
746        if (hasClass(childNode, "displayStepIcon")) {
747            childNode.addEventListener("click", function() {
748                clickStep(this.parentNode.id);
749            }, false);
750        }
751    }
752    for (var i = 0; i < tempElement.childNodes.length; i++){
753        var childNode = tempElement.childNodes[i];
754        if (hasClass(childNode, "displayStepIcon")) {
755            childNode.addEventListener("click", function() {
756                clickStep(this.parentNode.id);
757            }, false);
758        }
759    }
760   
761   
762   
763     
764// The alternative is to use event bubbling to capture the event on a higher level.
765// Basically, we bind a super-structure onclick event that uses e.target|| event.srcElement to determine which element to move and select.
766// http://stackoverflow.com/questions/29624/how-to-maintain-correct-javascript-event-after-using-clonenodetrue
767// Pro: clean implementation, less events binded.
768// Con: Difficult, this already works fine, probably tougher to use in conjunction with multifunctionality (sessions, surveys, questionsets, etc in one kind of editor)
769     
770}
771
772// WORK IN PROGRESS
773
774
775
776
777
778
779
780
781/******************/
782/* TEMP FUNCTIONS */
783/******************/
784
785// Temp function that creates a dummy question to test the insertNewQuestions function.
786function debug_addQuestion() {
787    // Derp, natuurlijk werkt de addQuestion call niet twee keer. Hij creeert twee keer exact hetzelfde object. Bottom line: het werkt. Nu de editing code voor deze questions gaan schrijven!
788    var response = [{
789        uid: "1234567890testuid",
790        title: "dummyQuestion",
791        description: "This is a dummy question, not a real one!",
792        type: "question"
793    }];
794   
795    var needsUpdating = [[
796    0,
797    "1234567890testuid",
798    "question"
799    ]];
800   
801    insertNewQuestions(response, needsUpdating);
802}
803
804// Temp function that articially switches content type when the page is already loaded, to easily test layout changes and content flow.
805function debug_switchType() {
806    var content = ge("seqContent");
807    if (sequencer.settings.content.contentType == "session") {
808        // Set to survey
809        sequencer.settings.content.contentType = "survey";
810        sequencer.settings.content.orientation = "vertical";
811        removeClass(content, "horizontal");
812        addClass(content, "vertical");
813    }
814    else {
815        // Set to session
816        sequencer.settings.content.contentType = "session";
817        sequencer.settings.content.orientation = "horizontal";
818        removeClass(content, "vertical");
819        addClass(content, "horizontal");
820    }
821}
Note: See TracBrowser for help on using the repository browser.