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

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