source: Dev/branches/jos-branch/js/sequencerScripts.js @ 266

Last change on this file since 266 was 240, checked in by fpvanagthoven, 13 years ago
File size: 38.6 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        //debugger;
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 setSequencerSize() {
126    var seqContent = ge("seqContent");
127    switch (sequencer.settings.content.orientation.toLowerCase()) {
128        case "vertical":
129            var availHeight = window.innerHeight;
130            var yOffset = getPos(seqContent).Y;
131            var bottomControlsHeight = 36;
132            var bottomMargin = 12;
133            sequencer.settings.content.height = (availHeight - yOffset - bottomControlsHeight - bottomMargin);
134            sequencer.settings.content.width = 665;
135            seqContent.style.height = sequencer.settings.content.height+"px";
136            seqContent.style.width = sequencer.settings.content.width+"px";
137            addClass(seqContent, "vertical");
138            break;
139        case "horizontal":
140            sequencer.settings.content.width = 800;
141            sequencer.settings.content.height = 125;
142            seqContent.style.height = sequencer.settings.content.height+"px";
143            seqContent.style.width = sequencer.settings.content.width+"px";
144            addClass(seqContent, "horizontal");
145            break;
146        default:
147            break;
148    }
149}
150
151function initSequencer() {
152    // 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.
153    // Example: stuff like editor orientation, width/height, content type, maxObjects contained, etc...
154   
155    // load settings fields first
156    var fContentType = ge("sContentTypeField");
157    sequencer.settings.content.contentType = fContentType.value;
158    //Then select settings from a few presets
159    switch (sequencer.settings.content.contentType.toLowerCase()) {
160        case "session":
161            sequencer.settings.content.orientation = "horizontal";
162            break;
163        case "survey":
164            sequencer.settings.content.orientation = "vertical";
165            break;
166        default:
167            break;
168    }
169    fContentType.parentNode.parentNode.removeChild(fContentType.parentNode);
170   
171    window.addEventListener("load", setSequencerSize, true);
172}
173
174// Updating, drawing, formatting
175
176function updateSequencer() {
177    // Code that manages drawing and adding of new visual representations of objects in the sequencer.
178
179    /*
180     * Description:
181     * This function updates the visual elements in the sequencer content view to match the current state of the sequencer.session.pipeline property.
182     * It queries the database for object properties via AJAX (returnStep/Display/.php), then inserts divider div's in between where needed.
183     */
184       
185    switch (sequencer.settings.content.contentType.toLowerCase()) {
186        case "session":
187            updateSequencer_Session();
188            break;
189        case "survey":
190            updateSequencer_Survey();
191            break;
192        default:
193            // Why would this even be called?
194            break;
195    }
196}
197
198function updateSequencer_Session() {
199    var content = ge("seqContentWrapper");
200    var requestString, needsUpdating;
201    var args;
202   
203    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
204        // Stop running this function if the pipeline does not contain any elements to draw
205        if (sequencer.session.pipeline.uids.length == 0 || !sequencer.session.pipeline.uids.length) return;
206        // First clear the entire content wrapper, just for safety and in case efficientUpdating is off
207        while (content.firstChild) {
208            content.removeChild(content.firstChild);
209        }
210        args = [];
211        needsUpdating = [];
212        for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) {
213            args.push({
214                uid: sequencer.session.pipeline.uids[i],
215                type: sequencer.session.pipeline.types[i]
216            });
217            needsUpdating.push(new Array(i, sequencer.session.pipeline.uids[i], sequencer.session.pipeline.types[i]));
218        }
219       
220        requestString = "args="+JSON.stringify(args);
221        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
222            content.removeChild(loadingGif);
223            insertNewObjects(result.responseText, needsUpdating);
224        }, true);
225        sequencer.state.loaded = true;
226        var loadingGif = ce("div");
227        loadingGif.innerHTML = "<img src='images/ui/ajax-loader-round.gif' style='float: left; margin:auto auto;' />";
228        content.appendChild(loadingGif);
229    }
230    else {
231        // This means that one or more steps are being added, not an entire pipeline's worth of them
232        needsUpdating = new Array();
233        args = [];
234        // Add steps that need updating to the needsUpdating array (index, uid, type).
235        for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) {
236            if (sequencer.session.pipeline.upToDate[i] == true) continue;
237            needsUpdating.push(new Array(i, sequencer.session.pipeline.uids[i], sequencer.session.pipeline.types[i]));
238            args.push({
239                uid: sequencer.session.pipeline.uids[i],
240                type: sequencer.session.pipeline.types[i]
241            });       
242        }
243       
244        requestString = "args="+JSON.stringify(args);
245        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
246            //console.log(result.responseText);
247            insertNewObjects(result.responseText, needsUpdating);
248        }, true);
249       
250        // Optional bit with the loading GIF
251        for (var i = 0; i < needsUpdating.length; i++) {
252            var loadingDiv = ce("div");
253            loadingDiv.className = "displayStep loading";
254            loadingDiv.innerHTML = "<img src='images/ui/ajax-loader-round.gif' />";
255            if (needsUpdating[i][0] > sequencer.state.numSteps-1) {
256                content.appendChild(loadingDiv);
257                sequencer.state.numSteps++;
258            }
259            else {
260                content.replaceChild(loadingDiv, content.childNodes[i][0]*2);
261            }
262        }
263        updateDividers();
264        // End optional
265    }
266}
267
268// THIS NEEDS SOME WORK! A LOT OF IT ACTUALLY!
269function updateSequencer_Survey() {
270   
271    var content = ge("seqContentWrapper");
272    var requestString, needsUpdating, args;
273    // 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.
274    var variables = sequencer.survey;
275   
276    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
277        // Stop running this function if the questions array does not contain any elements to draw
278        if (sequencer.survey.questions.uids.length == 0 || !sequencer.survey.questions.uids.length) return;
279        // First clear the entire content wrapper, just for safety and in case efficientUpdating is off
280        while (content.firstChild) {
281            content.removeChild(content.firstChild);
282        }
283        args = [];
284        needsUpdating = [];
285        for (var i = 0; i < sequencer.survey.questions.uids.length; i++) {
286            args.push({
287                uid: sequencer.survey.questions.uids[i],
288                type: "Question"
289            });
290            needsUpdating.push(new Array(i, sequencer.survey.questions.uids[i], "Question"));
291        }
292       
293        requestString = "args="+JSON.stringify(args);
294        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
295            //console.log(result.responseText);
296            content.removeChild(loadingGif);
297            insertNewObjects(result.responseText, needsUpdating);
298            sequencer.state.loaded = true;
299        }, true);
300        sequencer.state.loaded = false;
301        var loadingGif = ce("div");
302        loadingGif.innerHTML = "<img src='images/ui/ajax-loader-round.gif' style='float: left; margin:auto auto;' />";
303        content.appendChild(loadingGif);
304    }
305    else {
306
307        // This means that one or more steps are being added, not an entire pipeline's worth of them
308        needsUpdating = new Array();
309        args = [];
310        // Add steps that need updating to the needsUpdating array (index, uid, type).
311        for (var i = 0; i < sequencer.survey.questions.uids.length; i++) {
312            if (sequencer.survey.questions.upToDate[i] == true) continue;
313            needsUpdating.push(new Array(i, sequencer.survey.questions.uids[i], "Question"));
314            args.push({
315                uid: sequencer.survey.questions.uids[i],
316                type: "Question"
317            });       
318        }
319       
320        requestString = "args="+JSON.stringify(args);
321        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
322            //console.log(result.responseText);
323            insertNewObjects(result.responseText, needsUpdating);
324        }, true);
325       
326        // Optional bit with the loading GIF
327        if (sequencer.settings.content.contentType.toLowerCase() != "survey"){
328            for (var i = 0; i < needsUpdating.length; i++) {
329                var loadingDiv = ce("div");
330                loadingDiv.className = "displayStep loading";
331                loadingDiv.innerHTML = "<img src='images/ui/ajax-loader-round.gif' />";
332                if (needsUpdating[i][0] > sequencer.state.numSteps-1) {
333                    content.appendChild(loadingDiv);
334                    sequencer.state.numSteps++;
335                }
336                else {
337                    content.replaceChild(loadingDiv, content.childNodes[i][0]*2);
338                }
339            }
340        }
341        updateDividers();
342        // End optional
343       
344    }
345       
346}
347
348function updateDividers() {
349    // Function that checks for correct number of dividers in the pipeline, adds or deletes as needed.
350   
351    var content = ge("seqContentWrapper");
352    // Loop through all elements in seqContentWrapper
353    for (var i = 0; i < content.childNodes.length; i++) {
354        var element = content.childNodes[i];
355        // If the element is not a displayStep, continue with the next childNode.
356        if (!hasClass(element, "displayStep")) {
357            continue;
358        }
359        // Get the two elements next to the current element, if they don't exist or are not displaySteps, store false.
360        var lastElement = (element.previousSibling && element.previousSibling.nodeName == "DIV") ? element.previousSibling : false;
361        var nextElement = (element.nextSibling && element.nextSibling.nodeName == "DIV") ? element.nextSibling : false;
362        // If these elements exist, and they are not dividers, add an element in between.
363        if (lastElement != false) {
364            if (!hasClass(lastElement, "divider")){
365                var newDivider = ce("div");
366                addClass(newDivider, "divider");
367                addClass(newDivider, sequencer.settings.content.orientation);
368                content.insertBefore(newDivider, element);
369                delete newDivider;
370            }
371        }
372        if (nextElement != false) {
373            if (!hasClass(nextElement, "divider")){
374                var newDivider = ce("div");
375                addClass(newDivider, "divider");
376                addClass(newDivider, sequencer.settings.content.orientation);
377                content.insertBefore(newDivider, nextElement);
378                delete newDivider;
379            }
380        }
381    }
382   
383    // Because updateDividers is always called last, we set updating to false here, so the user can again issue commands to the pipeline
384    sequencer.state.updating = false;   // Re-enable new actions
385}
386
387// Adding new objects
388
389function submitToolbox(type) {
390    // Handles new object creation code when user clicks on a toolbox button
391   
392    // Do not accept new creation requests if the sequencer is still updating a previous object.
393    if (sequencer.state.updating == true) return;
394    sequencer.state.updating = true;
395    deselectStep();   
396    var c = "objectToCreate="+type;
397    var u = "createObject.php";
398       
399    newAjaxRequest(c, u, function(result) {
400        sequencer.session.pipeline.uids.push(removeNL(result.responseText));
401        sequencer.session.pipeline.types.push(type);
402        sequencer.session.pipeline.upToDate.push(false);
403        updateSequencer();
404    }, true);
405}
406
407function insertNewObjects(responseText, needsUpdating) {
408    //console.log(responseText);
409    // Container function that calls different insertNewX() functions depending on content type. Called from updateSequencer().
410    //console.log(responseText);
411    var response = JSON.parse(responseText);
412    // 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.
413    switch (sequencer.settings.content.contentType.toLowerCase()) {
414        case "session":
415            insertNewSteps(response, needsUpdating);
416            break;
417        case "survey":
418            insertNewQuestions(response, needsUpdating);
419            break;
420        default:
421            break;
422               
423    }
424}
425
426function addQuestion_Click() {
427    if (sequencer.state.updating == true) return;
428    sequencer.state.updating = true;
429    deselectStep();   
430    var c = "objectToCreate=question";
431    var u = "createObject.php";
432       
433    newAjaxRequest(c, u, function(result) {
434        //console.log(result.responseText);
435        sequencer.survey.questions.uids.push(removeNL(result.responseText));
436        sequencer.survey.questions.upToDate.push(false);
437        updateSequencer();
438    }, true);
439}
440
441//> Session specific
442
443function insertNewSteps(response, needsUpdating) {
444    /*
445     * This is a function displaying how to handle the visual object representation in solely javascript.
446     * Communication of relevant variables between PHP and JS happens in JSON format.
447     * PHP returns a JSON array of objects to be created by JS
448     * JS then loops through this array and creates DIVS to be inserted into the sequencer.
449     * These are inserted at the position needsUpdating gives us.
450     */
451    if (!response || !response.length > 0) return;
452    //console.log(response);
453   
454    var content = ge("seqContentWrapper");
455    // Remove optional loading images
456    for (var  i = 0; i < content.childNodes.length; i++) {
457        if (hasClass(content.childNodes[i], "loading")) {
458            content.removeChild(content.childNodes[i]);
459        }
460    }
461    // End optional
462   
463    for (var i = 0; i < response.length; i++) {
464        var tempDiv = ce("div");
465        tempDiv.id = response[i].uid;
466        tempDiv.className = "displayStep";
467        var divImageContainer = ce("div");
468        divImageContainer.className = "displayStepIcon";
469        divImageContainer.addEventListener("click", function(){
470            clickStep(this.parentNode.id);
471        }, false);
472        var divImage = ce("img");
473        divImage.src = "images/icons/"+response[i].ObjectType.toLowerCase()+".png";
474        divImageContainer.appendChild(divImage);
475        tempDiv.appendChild(divImageContainer);
476        var divLabel = ce("p");
477        divLabel.innerHTML = response[i].title;
478        tempDiv.appendChild(divLabel);
479       
480        // 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.
481        for (var j = needsUpdating.length-1; j >= 0; j--) {
482            if (needsUpdating[j][1] != response[i].uid) continue;
483            if (needsUpdating[j][0] > sequencer.state.numSteps-1) {
484                content.appendChild(tempDiv);
485            }
486            else {
487                content.replaceChild(tempDiv, content.childNodes[j][0]*2);
488            }
489            sequencer.session.pipeline.upToDate[needsUpdating[j][0]] = true;
490        }
491    }
492   
493    updateDividers();
494}
495
496function deleteStep() {
497    // Delete a step from both the pipeline.uids variable and its visual representation from the sequencer. Does not actually delete object from database!
498   
499    // check if a step is selected
500    if (sequencer.state.selectedObject.uid == null) {
501        return;
502    }
503    var uid = sequencer.state.selectedObject.uid;
504    // deselect the step to reset the info panel and selection code
505    deselectStep();
506    // splice the step's data from the pipeline
507    var index = sequencer.session.pipeline.uids.indexOf(uid);
508    if (index >= 0 && index < sequencer.session.pipeline.uids.length) {
509        sequencer.session.pipeline.uids.splice(index, 1);
510        sequencer.session.pipeline.types.splice(index, 1);
511        sequencer.session.pipeline.upToDate.splice(index, 1);
512        // Then delete the step visually
513        var element = ge(uid);
514        var divider;
515        if (!element.nextSibling) {
516            // the element is at the end of the pipeline
517            // therefore we remove the previous divider.
518            // Note: it could also be the only element left in the pipeline!
519            divider = (element.previousSibling) ? element.previousSibling : false;
520            if (divider != false) {
521                divider.parentNode.removeChild(divider);
522            }
523        }
524        else {
525            // the element is at any position except the last, therefore we remove the next divider
526            divider = (element.nextSibling) ? element.nextSibling : false;
527            if (divider != false) {
528                divider.parentNode.removeChild(divider);
529            }
530        }
531       
532        // Finally, remove the element itself.
533        element.parentNode.removeChild(element);
534        sequencer.state.numSteps--;
535       
536    }
537}
538
539//> Survey specific
540
541function insertNewQuestions(response, needsUpdating) {
542    //TODO: QuestionDisplay ook classbased maken zoals de questionEditor?
543   
544    //Code that inserts or replaces new object displays in the sequencer. Question version.
545    var content = ge("seqContentWrapper");
546    // Loop through returned question objects
547    for (var i = 0; i < response.length; i++) {
548       
549        /*
550         * The following block of code defines the layout and composition of the question display
551         */
552       
553        // Define the outer frame
554        var frameDiv = ce("div");
555        frameDiv.className = "smallFrame question";
556        frameDiv.id = response[i].uid;
557        var titleDiv = ce("div");
558        titleDiv.className = "smallTitle";
559        var numberDiv = ce("div");
560        numberDiv.className = "listNumber";
561        numberDiv.innerHTML = i.toString();
562        titleDiv.appendChild(numberDiv);
563        titleDiv.innerHTML += response[i].uid;
564        frameDiv.appendChild(titleDiv);
565       
566        // The frame now has a header bar
567        // On to the content frame
568        // Will use new "ce();" function, shorthand for ce
569       
570        var contentDiv = ce("div");
571        contentDiv.className = "content";
572        var questionBody = ce("p");
573        questionBody.innerHTML = response[i].description;
574        var questionParamsDiv = ce("div");
575        questionParamsDiv.className = "questionParamsView";
576        questionParamsDiv.innerHTML = "Object type: "+response[i].type;
577        contentDiv.appendChild(questionBody);
578        contentDiv.appendChild(questionParamsDiv);
579        frameDiv.appendChild(contentDiv);
580       
581        // And finally the controls div
582        var controlsDiv = ce("div");
583        controlsDiv.className = "controls";
584        var editButton = ce("input");
585        var removeButton = ce("input");
586        editButton.value = "Edit";
587        removeButton.value = "Remove";
588        editButton.className = "smallButton";
589        removeButton.className = "smallButton";
590        editButton.addEventListener("click", function(e){
591            var targ;
592            if (!e) e = window.event;
593            if (e.target) targ = e.target;
594            else if (e.srcElement) targ = e.srcElement;
595           
596            do {
597                targ = targ.parentNode;
598            } while (!hasClass(targ, "question"));
599           
600            questionEditor.editQuestion(targ.id);
601            e.stopPropagation();
602        }, false);
603        removeButton.addEventListener("click", function(e){
604            alert('Removing not yet supported!');
605            e.stopPropagation();
606        }, false);
607        controlsDiv.appendChild(editButton);
608        controlsDiv.appendChild(removeButton);
609        frameDiv.addEventListener("click", function(){
610            clickStep(this.id);
611        }, false);
612        frameDiv.appendChild(controlsDiv);
613       
614        /*
615         * This is where the layout code ends
616         * We proceed to insertion of the created DIV into the document
617         */
618       
619        // We now have a full question display DIV contained in the frameDiv variable. We should now add this to the sequencer content.
620        for (var j = needsUpdating.length - 1; j >= 0; j--) {
621            if (needsUpdating[j][1] != response[i].uid) continue;
622            if (needsUpdating[j][0] > sequencer.state.numSteps-1) {
623                content.appendChild(frameDiv);
624                sequencer.state.numSteps++;
625            }
626            else {
627                if (content.childNodes[needsUpdating[j][0]*2]) {
628                    content.replaceChild(frameDiv, content.childNodes[needsUpdating[j][0]*2]);
629                }
630                else {
631                    content.appendChild(frameDiv);
632                    // HACK TIME!
633                    for (var s = 0; s < content.childNodes.length; s++) {
634                        if (hasClass(content.childNodes[s], "loading")) {
635                            content.childNodes[s].parentNode.removeChild(content.childNodes[s]);
636                        }
637                    }
638                    // Serieus, dit moet beter...
639                }
640            }
641            sequencer.survey.questions.upToDate[needsUpdating[j][0]] = true;
642           
643        }
644    }
645   
646    sequencer.state.updating = false;   //re-enable user commands
647}
648
649function saveSurvey(confirmSave) {
650    // Sends an AJAX request to the PHP server that saves the current object.
651    var answer = (confirmSave == true) ? confirm("Save changes?") : true;
652    if (answer == false) return;
653   
654    // Check for object type being edited, adjust requestString and target URL to this information.
655    var request = new Object(), url, requestString;
656    switch (sequencer.settings.content.contentType.toLowerCase()) {
657        case "survey":
658            url = "saveSurvey.php";
659            request.title = sequencer.survey.title;
660            request.uid = sequencer.survey.uid;
661            request.description = sequencer.survey.description;
662            request.questions = new Object();
663            request.questions.uids = sequencer.survey.questions.uids;
664            requestString = "args="+JSON.stringify(request);
665            //console.log(request);
666            newAjaxRequest(requestString, url, function(result){
667                console.log(result.responseText);
668            }, true);
669            break;
670        case "blaaaat":
671            url = "savesession.php";
672            request.pipeline.uids = sequencer.session.pipeline.uids;
673            request.pipeline.types = sequencer.session.pipeline.types;
674            break;
675    }
676}
677
678// general functions and user actions
679
680function clickStep(uid) {
681    // Handles selection of steps
682   
683    if (uid == sequencer.state.selectedObject.uid) {  // user deselected a currently selected step.
684        deselectStep();
685    }
686    else {
687        if (sequencer.state.selectedObject.uid != undefined && sequencer.state.selectedObject.uid != "") {
688            // Change selection if something is already selected
689            deselectStep();
690            selectStep(uid);
691        }
692        else {
693            // Make new selection if nothing was selected
694            selectStep(uid);
695        }       
696    }
697}
698
699function selectStep(uid) {
700    // Called from clickStep(), manages CSS class assignment and updating of state variables. Also calls for info panel update.
701    debugger;
702    var element = ge(uid);
703    if (element) {
704        addClass(element, "selected");
705        var type;
706        switch (sequencer.settings.content.contentType.toLowerCase()) {
707            case "session":
708                type = sequencer.session.pipeline.types[sequencer.session.pipeline.uids.indexOf(uid)];
709                break;
710            case "survey":
711                type = "Question";
712                break;
713            default:
714                //Dunno
715                break;
716        }
717        (type != "Question") ? ajaxInfoRequest(uid, ge("infoPanelContent"), type) : type=type /*This does nothing*/;
718        sequencer.state.selectedObject.uid = uid;
719        sequencer.state.selectedObject.index = null;  //Don't know how to do this yet.
720    }
721}
722
723function deselectStep() {
724    // Called from clickStep(). Handles unassignment and updating of state variables. Clears info panel.
725   
726    if (!sequencer.state.selectedObject.uid) return;
727    var element = ge(sequencer.state.selectedObject.uid);
728    removeClass(element, "selected");
729    sequencer.state.selectedObject.uid = null;
730    sequencer.state.selectedObject.index = null;
731    var infoPanel = ge("infoPanelContent");
732    if (infoPanel) {
733        while (infoPanel.firstChild) infoPanel.removeChild(infoPanel.firstChild);
734    }
735}
736
737
738
739function savePipeline (confirmSave) {
740    // Sends an AJAX request to the PHP server that saves the current pipeline. Does not yet support surveys.
741   
742    // First check if user should confirm save action or not.
743    var answer;
744    if (confirmSave == true) {
745        answer = confirm("Save changes to pipeline?");
746    }
747    else {
748        answer = true;
749    }
750    if (answer == false) return;
751    // Then compose requestString for savesession.php, containing pipeline uids, types and the session uid.
752    // TODO: should eventually include stuff like session name as well!
753    var requestString = "uids=";
754    requestString += arrayToString(sequencer.session.pipeline.uids, ",");
755    requestString = requestString.slice(0, requestString.length - 1);   // remove trailing commas
756    requestString += "&types=";
757    requestString += arrayToString(sequencer.session.pipeline.types, ",");
758    requestString = requestString.slice(0, requestString.length - 1);   // remove trailing commas
759    requestString += "&sessionUid=";
760    requestString += sequencer.session.uid;
761    //console.log(requestString);
762    newAjaxRequest(requestString, "savesession.php", function(result){
763        console.log(result.responseText);
764    }, true);
765}
766
767function editStep() {
768    // Redirects the browser to the appropriate editor for the selected step type.
769   
770    // first save
771    savePipeline(false);
772    // Then post relevant information so the next editor page knows what object it is supposed to be editing.
773    var postForm = ce("form");
774    var type = sequencer.session.pipeline.types[sequencer.session.pipeline.uids.indexOf(sequencer.state.selectedObject.uid)];
775    postForm.action = type.toLowerCase()+"Editor.php";
776    postForm.method = "POST";
777    var objectUid = ce("input");
778    objectUid.type = "hidden";
779    objectUid.name = "objectUid";
780    objectUid.value = sequencer.state.selectedObject.uid;
781    postForm.appendChild(objectUid);
782    postForm.submit();
783}
784
785function moveStep (direction) {
786    // Moves the selected step up/down (left/right) in the pipeline.
787   
788    // Check if a step is selected
789    if (sequencer.state.selectedObject.uid == null || direction == null) return;
790    // Check if the step is not at either end of the pipeline
791    var index = sequencer.session.pipeline.uids.indexOf(sequencer.state.selectedObject.uid);
792    if (index == -1) return;
793    if ((index < 0) || (index >= sequencer.session.pipeline.uids.length) || (index == 0 && direction == -1) || (index == sequencer.session.pipeline.uids.length - 1 && direction == 1)) {
794        alert("Cannot move out of bounds!");
795        return;
796    }
797   
798    // Find the two elements in the editor content display
799    var content = ge("seqContentWrapper");
800    var element = ge(sequencer.session.pipeline.uids[index]);
801    var otherElement = ge(sequencer.session.pipeline.uids[index+direction]);
802    // First buffer the two elements
803    var tempElement = element.cloneNode(true);
804    var tempOtherElement = otherElement.cloneNode(true);
805    var placeHolderElement = ce("div");
806    placeHolderElement.id = "placeholder_element";
807    content.replaceChild(placeHolderElement, otherElement);
808    content.replaceChild(tempOtherElement, element);
809    content.replaceChild(tempElement, placeHolderElement);
810    //This should work.
811    // A-B     Start positions, backup to tA and tB
812    // A-X     Replace B with placeholder X
813    // B-X     Replace A with tB
814    // B-A     Replace placeholder X with tA.
815    // The two elements are now swapped.
816     
817    // Now swap the array entries.
818    sequencer.session.pipeline.uids[index] = sequencer.session.pipeline.uids.splice(index+direction, 1, sequencer.session.pipeline.uids[index])[0];
819    sequencer.session.pipeline.types[index] = sequencer.session.pipeline.types.splice(index+direction, 1, sequencer.session.pipeline.types[index])[0];
820   
821    // 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.
822    for (var i = 0; i < tempOtherElement.childNodes.length; i++){
823        var childNode = tempOtherElement.childNodes[i];
824        if (hasClass(childNode, "displayStepIcon")) {
825            childNode.addEventListener("click", function() {
826                clickStep(this.parentNode.id);
827            }, false);
828        }
829    }
830    for (var i = 0; i < tempElement.childNodes.length; i++){
831        var childNode = tempElement.childNodes[i];
832        if (hasClass(childNode, "displayStepIcon")) {
833            childNode.addEventListener("click", function() {
834                clickStep(this.parentNode.id);
835            }, false);
836        }
837    }
838   
839   
840   
841     
842    // The alternative is to use event bubbling to capture the event on a higher level.
843    // Basically, we bind a super-structure onclick event that uses e.target|| event.srcElement to determine which element to move and select.
844    // http://stackoverflow.com/questions/29624/how-to-maintain-correct-javascript-event-after-using-clonenodetrue
845    // Pro: clean implementation, less events binded.
846    // Con: Difficult, this already works fine, probably tougher to use in conjunction with multifunctionality (sessions, surveys, questionsets, etc in one kind of editor)
847     
848}
849
850// WORK IN PROGRESS
851
852
853
854
855
856
857
858
859/******************/
860/* TEMP FUNCTIONS */
861/******************/
862
863// Temp function that creates a dummy question to test the insertNewQuestions function.
864// NOTE: CAN BE REMOVED!
865function debug_addQuestion() {
866    // 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!
867    var response = [{
868            uid: "1234567890testuid",
869            title: "dummyQuestion",
870            description: "This is a dummy question, not a real one!",
871            type: "question"
872        }];
873   
874    var needsUpdating = [[
875            0,
876            "1234567890testuid",
877            "question"
878        ]];
879   
880    insertNewQuestions(response, needsUpdating);
881}
882
883// Temp function that articially switches content type when the page is already loaded, to easily test layout changes and content flow.
884// NOTE: CAN BE REMOVED!
885function debug_switchType() {
886    var content = ge("seqContent");
887    if (sequencer.settings.content.contentType == "session") {
888        // Set to survey
889        sequencer.settings.content.contentType = "survey";
890        sequencer.settings.content.orientation = "vertical";
891        removeClass(content, "horizontal");
892        addClass(content, "vertical");
893    }
894    else {
895        // Set to session
896        sequencer.settings.content.contentType = "session";
897        sequencer.settings.content.orientation = "horizontal";
898        removeClass(content, "vertical");
899        addClass(content, "horizontal");
900    }
901}
Note: See TracBrowser for help on using the repository browser.