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

Last change on this file since 238 was 238, checked in by fpvanagthoven, 13 years ago

-selectObject is een algemene object selector. Kan gebruikt worden vanuit het mainmenu om in de verschillende editors te komen, en kan eventueel ook onder de "add existing" knop kopen te staan binnen die editors.

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