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

Last change on this file since 183 was 183, checked in by fpvanagthoven, 13 years ago
File size: 22.8 KB
Line 
1/*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5
6var sequencer = {   // GLOBAL VAR TO STORE SEQUENCER SETTINGS!
7    uid: "",                // The unique id of this sequencer (not DB related!). This will help to avoid global var conflicts. Assign this randomly! (STRING)
8    session: {              // Properties of the currently loaded session
9        title: "",              // Title or name (STRING)
10        uid: "",                // Database UID of the current session (STRING)
11        pipeline: {             // Pipeline
12            uids: [],               // Uids of objects in pipeline (STRING)
13            types: [],              // Types of objects in pipeline (STRING)
14            upToDate: []            // Whether or not object displays are up to date (BOOL)
15        }
16    },
17    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!]
18        title: "",              // Title or the name of the survey
19        uid: "",                // Uid of the survey
20        questions: {            // Properties of the questions contained within this survey
21            uids: [],               // An array of uids of the questions, in the order that they appear in the survey
22            upToDate: []            // Whether or not a certain question needs an update
23        }
24    },
25    state: {                // Operating state of the sequencer
26        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 selectedStep property.
27        numSteps: 0,            // Number of steps currently drawn in the editor (not necessarily same as number of steps in pipeline!) (INTEGER)
28        loaded: false,          // Whether or not the sequencer content has been updated for the first time (BOOL)
29        selectedStep: {         // Properties of the currently selected step
30            uid: "",                // UID of this step (STRING)
31            index: null             // The 'index' of this step in the current sequencer view (NOT the pipeline!) (INTEGER)
32        }       
33    },
34    settings: {             // Various settings to determine the workings of the sequencer
35        content: {              // Properties related to the content view of the sequencer
36            contentType: "session", // Type of the loaded parent object (STRING)
37            width: null,            // Width of the viewing area (INTEGER)
38            height: null,           // Height of the viewing area (INTEGER)
39            maxObjects: null,       // The maximum number of content elements to be displayed at once time (INTEGER)
40            orientation: "horizontal"        // Whether the editor should be a vertical or horizontal editor (STRING)
41        },
42        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
43    }
44}
45
46
47function SubmitToolbox(type) {
48    deselectStep();
49    var c = "objectToCreate="+type;
50    var u = "createObject.php";
51       
52    newAjaxRequest(c, u, function(result) {
53        sequencer.session.pipeline.uids.push(removeNL(result.responseText));
54        sequencer.session.pipeline.types.push(type);
55        sequencer.session.pipeline.upToDate.push(false);
56        updateSequencer();
57    }, true);
58}
59
60function clickStep(uid) {
61    if (uid == sequencer.state.selectedStep.uid) {  // user deselected a currently selected step.
62        deselectStep();
63    }
64    else {
65        if (sequencer.state.selectedStep.uid != undefined && sequencer.state.selectedStep.uid != "") {
66            deselectStep();
67            selectStep(uid);
68        }
69        else {
70            selectStep(uid);
71        }       
72    }
73}
74
75function selectStep(uid) {
76    var element = document.getElementById(uid);
77    if (element) {
78        addClass(element, "selected");
79        var type = sequencer.session.pipeline.types[sequencer.session.pipeline.uids.indexOf(uid)];
80        ajaxInfoRequest(uid, document.getElementById("infoPanelContent"), type);
81        sequencer.state.selectedStep.uid = uid;
82        sequencer.state.selectedStep.index = null;  //Don't know how to do this yet.
83    }
84}
85
86function deleteStep() {
87    // check if a step is selected
88    if (sequencer.state.selectedStep.uid == null) {
89        return;
90    }
91    var uid = sequencer.state.selectedStep.uid;
92    // deselect the step to reset the info panel and selection code
93    deselectStep();
94    // splice the step's data from the pipeline
95    var index = sequencer.session.pipeline.uids.indexOf(uid);
96    if (index >= 0 && index < sequencer.session.pipeline.uids.length) {
97        sequencer.session.pipeline.uids.splice(index, 1);
98        sequencer.session.pipeline.types.splice(index, 1);
99        sequencer.session.pipeline.upToDate.splice(index, 1);
100        // Then delete the step visually
101        var element = document.getElementById(uid);
102        var divider;
103        if (!element.nextSibling) {
104            // the element is at the end of the pipeline
105            // therefore we remove the previous divider.
106            // Note: it could also be the only element left in the pipeline!
107            divider = (element.previousSibling) ? element.previousSibling : false;
108            if (divider != false) {
109                divider.parentNode.removeChild(divider);
110            }
111        }
112        else {
113            // the element is at any position except the last, therefore we remove the next divider
114            divider = (element.nextSibling) ? element.nextSibling : false;
115            if (divider != false) {
116                divider.parentNode.removeChild(divider);
117            }
118        }
119       
120        // Finally, remove the element itself.
121        element.parentNode.removeChild(element);
122        sequencer.state.numSteps--;
123       
124    }
125}
126
127function deselectStep() {
128    if (!sequencer.state.selectedStep.uid) return;
129    var element = document.getElementById(sequencer.state.selectedStep.uid);
130    removeClass(element, "selected");
131    sequencer.state.selectedStep.uid = null;
132    sequencer.state.selectedStep.index = null;
133    var infoPanel = document.getElementById("infoPanelContent");
134    while (infoPanel.firstChild) infoPanel.removeChild(infoPanel.firstChild);
135}
136
137function ajaxInfoRequest(uid, el, type) {
138    var c = "uid="+uid;
139    c += "&type="+type;
140    var u = "getInfo.php";
141    newAjaxRequest(c, u, function(result) {
142        el.innerHTML = result.responseText;
143    }, true);
144}
145
146function updateSequencer() {
147
148    /*
149     * Description:
150     * This function updates the visual elements in the sequencer content view to match the current state of the sequencer.session.pipeline property.
151     * It queries the database for object properties via AJAX (returnStep/Display/.php), then inserts divider div's in between where needed.
152     */
153    var content = document.getElementById("seqContentWrapper");
154    var requestString, needsUpdating;
155    var args;
156    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
157        // Stop running this function if the pipeline does not contain any elements to draw
158        if (sequencer.session.pipeline.uids.length == 0 || !sequencer.session.pipeline.uids.length) return;
159        // First clear the entire content wrapper, just for safety and in case efficientUpdating is off
160        while (content.firstChild) {
161            content.removeChild(content.firstChild);
162        }
163        args = [];
164        needsUpdating = [];
165        for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) {
166            args.push({
167                uid: sequencer.session.pipeline.uids[i],
168                type: sequencer.session.pipeline.types[i]
169            });
170            needsUpdating.push(new Array(i, sequencer.session.pipeline.uids[i], sequencer.session.pipeline.types[i]));
171        }
172       
173        requestString = "args="+JSON.stringify(args);
174        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
175            content.removeChild(loadingGif);
176            insertNewObjects(result.responseText, needsUpdating);
177        }, true);
178        sequencer.state.loaded = true;
179        var loadingGif = document.createElement("div");
180        loadingGif.innerHTML = "<img src='images/ui/ajax-loader-round.gif' style='float: left; margin:auto auto;' />";
181        content.appendChild(loadingGif);
182    }
183    else {
184        // This means that one or more steps are being added, not an entire pipeline's worth of them
185        needsUpdating = new Array();
186        args = [];
187        // Add steps that need updating to the needsUpdating array (index, uid, type).
188        for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) {
189            if (sequencer.session.pipeline.upToDate[i] == true) continue;
190            needsUpdating.push(new Array(i, sequencer.session.pipeline.uids[i], sequencer.session.pipeline.types[i]));
191            args.push({
192                uid: sequencer.session.pipeline.uids[i],
193                type: sequencer.session.pipeline.types[i]
194            });       
195        }
196       
197        requestString = "args="+JSON.stringify(args);
198        newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){
199            insertNewObjects(result.responseText, needsUpdating);
200        }, true);
201       
202        // Optional bit with the loading GIF
203        for (var i = 0; i < needsUpdating.length; i++) {
204            var loadingDiv = document.createElement("div");
205            loadingDiv.className = "displayStep loading";
206            loadingDiv.innerHTML = "<img src='images/ui/ajax-loader-round.gif' />";
207            if (needsUpdating[i][0] > sequencer.state.numSteps-1) {
208                content.appendChild(loadingDiv);
209                sequencer.state.numSteps++;
210            }
211            else {
212                content.replaceChild(loadingDiv, content.childNodes[i][0]*2);
213            }
214        }
215        updateDividers();
216    // End optional
217    }
218    console.log(sequencer);
219}
220
221function loadSequencer() {
222    /*
223 * Description:
224 * Load data from hidden fields (put there by PHP), store them in the global var "sequencer" (as well as several initialization properties),
225 * then remove the hidden fields from the HTML document tree.
226 */
227   
228    // Load hidden fields and set required properties in global object var.
229    try {
230        // settings fields first
231        initEditor();
232       
233        // Content-related fields next
234        var fPipelineString = document.getElementById("pipelineStringField");
235        var fPipelineTypes = document.getElementById("pipelineTypeField");
236        var fSessionUid = document.getElementById("sessionField");
237        var fSessionTitle = document.getElementById("sessionTitleField");
238   
239        sequencer.session.title = fSessionTitle.value;
240        sequencer.session.uid = fSessionUid.value;
241        sequencer.session.pipeline.uids = stringToArray(fPipelineString.value, ",");
242        sequencer.session.pipeline.types = stringToArray(fPipelineTypes.value, ",");
243        sequencer.session.pipeline.upToDate = new Array();
244       
245        for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) {
246            sequencer.session.pipeline.upToDate.push(true);
247        }
248        sequencer.state.numSteps = 0;
249        sequencer.state.loaded = false;
250        sequencer.settings.content.orientation = "h";
251        sequencer.settings.efficientUpdating = true;
252    }
253    catch (e) {
254        // Alert developer of any errors while setting these variables
255        for (error in e) alert(error.message);
256    }
257   
258    // Then remove the hidden fields from the HTML document
259    var hiddenInputs = document.getElementById("hiddenInputs");
260    hiddenInputs.parentNode.removeChild(hiddenInputs);
261   
262    // finally, run updateSequencer to refresh the visual display of the pipeline
263   
264    updateSequencer();
265}
266
267function insertNewObjects(responseText, needsUpdating) {
268    var response = JSON.parse(responseText);
269    // 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.
270    switch (response[0].type.toLowerCase()) {
271        case "survey": case "application": case "dashboard":
272            insertNewSteps(response, needsUpdating);
273            break;
274        case "question":
275            insertNewQuestions(response, needsUpdating);
276            break;
277        default:
278            break;
279               
280    }
281}
282
283function insertNewSteps(response, needsUpdating) {
284    /*
285 * This is a test function displaying how to handle the visual object representation in solely javascript.
286 * Communication of relevant variables between PHP and JS happens in JSON format.
287 * PHP returns a JSON array of objects to be created by JS
288 * JS then loops through this array and creates DIVS to be inserted into the sequencer.
289 * These are inserted at the position needsUpdating gives us.
290 */
291    var content = document.getElementById("seqContentWrapper");
292    // Remove optional loading images
293    for (var  i = 0; i < content.childNodes.length; i++) {
294        if (hasClass(content.childNodes[i], "loading")) {
295            content.removeChild(content.childNodes[i]);
296        }
297    }
298    // End optional
299   
300    for (var i = 0; i < response.length; i++) {
301        var tempDiv = document.createElement("div");
302        tempDiv.id = response[i].uid;
303        tempDiv.className = "displayStep";
304        var divImageContainer = document.createElement("div");
305        divImageContainer.className = "displayStepIcon";
306        divImageContainer.addEventListener("click", function(){
307            clickStep(this.parentNode.id);
308        }, false);
309        var divImage = document.createElement("img");
310        divImage.src = "images/icons/"+response[i].type.toLowerCase()+".png";
311        divImageContainer.appendChild(divImage);
312        tempDiv.appendChild(divImageContainer);
313        var divLabel = document.createElement("p");
314        divLabel.innerHTML = response[i].title;
315        tempDiv.appendChild(divLabel);
316       
317        // 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.
318        for (var j = needsUpdating.length-1; j >= 0; j--) {
319            if (needsUpdating[j][1] != response[i].uid) continue;
320            if (needsUpdating[j][0] > sequencer.state.numSteps-1) {
321                content.appendChild(tempDiv);
322            }
323            else {
324                content.replaceChild(tempDiv, content.childNodes[j][0]*2);
325            }
326            sequencer.session.pipeline.upToDate[needsUpdating[j][0]] = true;
327        }
328    }
329   
330    updateDividers();
331}
332
333function insertNewQuestions(response, needsUpdating) {
334    var content = document.getElementById("seqcContentWrapper");
335    // Loop through returned question objects
336    for (var i = 0; i < response.length; i++) {
337        // Define the outer frame
338        var frameDiv = document.createElement("div");
339        frameDiv.classname = "smallFrame question";
340        frameDiv.id = response[i].uid;
341        var titleDiv = document.createElement("div");
342        titleDiv.className = "smallTitle";
343        var numberDiv = document.createElement("div");
344        numberDiv.className = "listNumber";
345        numberDiv.innerHTML = i.toString();
346        titleDiv.appendChild(numberDiv);
347        titleDiv.innerHTML += response[i].uid;
348        frameDiv.appendChild(titleDiv);
349       
350        // The frame now has a header bar
351        // On to the content frame
352        // Will use new "ce();" function, shorthand for document.createElement
353       
354        var contentDiv = ce("div");
355        contentDiv.className = "content";
356        var questionBody = ce("p");
357        questionBody.innerHTML = response[i].description;
358        var questionParamsDiv = ce("div");
359        questionParamsDiv.className = "questionParamsView";
360        questionParamsDiv.innerHTML = "Object type: "+response[i].type;
361        contentDiv.appendChild(questionBody);
362        contentDiv.appendChild(questionParamsDiv);
363        frameDiv.appendChild(contentDiv);
364       
365        // And finally the controls div
366        var controlsDiv = ce("div");
367        controlsDiv.className = "controls";
368        var editButton = ce("input");
369        var removeButton = ce("input");
370        editButton.value = "Edit";
371        removeButton.value = "Remove";
372        editButton.className = "smallButton";
373        removeButton.className = "smallButton";
374        editButton.addEventListener("click", function(e){
375            alert('Editing not yet supported!');
376        }, false);
377        removeButton.addEventListener("click", function(e){
378            alert('Removing not yet supported!');
379        }, false);
380        controlsDiv.appendChild(editButton);
381        controlsDiv.appendChild(removeButton);
382        frameDiv.appendChild(controlsDiv);
383       
384    // We now have a full question display DIV contained in the frameDiv variable. We should now add this to the sequencer content.
385       
386    }
387}
388
389function updateDividers() {
390    var content = document.getElementById("seqContentWrapper");
391    for (var i = 0; i < content.childNodes.length; i++) {
392        var element = content.childNodes[i];
393        if (!hasClass(element, "displayStep")) {
394            continue;
395        }
396        var lastElement = (element.previousSibling && element.previousSibling.nodeName == "DIV") ? element.previousSibling : false;
397        var nextElement = (element.nextSibling && element.nextSibling.nodeName == "DIV") ? element.nextSibling : false;
398        if (lastElement != false) {
399            if (!hasClass(lastElement, "divider")){
400                var newDivider = document.createElement("div");
401                addClass(newDivider, "divider");
402                addClass(newDivider, sequencer.settings.content.orientation);
403                content.insertBefore(newDivider, element);
404                delete newDivider;
405            }
406        }
407        if (nextElement != false) {
408            if (!hasClass(nextElement, "divider")){
409                var newDivider = document.createElement("div");
410                addClass(newDivider, "divider");
411                addClass(newDivider, sequencer.settings.content.orientation);
412                content.insertBefore(newDivider, nextElement);
413                delete newDivider;
414            }
415        }
416    }
417}
418
419function savePipeline (confirmSave) {
420    debugger;
421    var answer;
422    if (confirmSave == true) {
423        answer = confirm("Save changes to pipeline?");
424    }
425    else {
426        answer = true;
427    }
428    if (answer == false) return;
429   
430    var requestString = "uids=";
431    requestString += arrayToString(sequencer.session.pipeline.uids, ",");
432    requestString = requestString.slice(0, requestString.length - 1);   // remove trailing commas
433    requestString += "&types=";
434    requestString += arrayToString(sequencer.session.pipeline.types, ",");
435    requestString = requestString.slice(0, requestString.length - 1);   // remove trailing commas
436    requestString += "&sessionUid=";
437    requestString += sequencer.session.uid;
438    console.log(requestString);
439    newAjaxRequest(requestString, "savesession.php", function(result){
440        console.log(result.responseText);
441    }, true);
442}
443
444function editStep() {
445    // first save
446   
447    //savePipeline(false);
448   
449    var postForm = document.createElement("form");
450    var type = sequencer.session.pipeline.types[sequencer.session.pipeline.uids.indexOf(sequencer.state.selectedStep.uid)];
451    postForm.action = type.toLowerCase()+"Editor.php";
452    postForm.method = "POST";
453    var objectUid = document.createElement("input");
454    objectUid.type = "hidden";
455    objectUid.name = "objectUid";
456    objectUid.value = sequencer.state.selectedStep.uid;
457    postForm.appendChild(objectUid);
458    postForm.submit();
459}
460
461function moveStep (direction) {
462    // Check if a step is selected
463   
464    if (sequencer.state.selectedStep.uid == null || direction == null) return;
465    // Check if the step is not at either end of the pipeline
466    var index = sequencer.session.pipeline.uids.indexOf(sequencer.state.selectedStep.uid);
467    if ((index < 0) || (index >= sequencer.session.pipeline.uids.length) || (index == 0 && direction == -1) || (index == sequencer.session.pipeline.uids.length - 1 && direction == 1)) {
468        alert("Cannot move out of bounds!");
469        return;
470    }
471   
472    // Find the two elements in the editor content display
473    var content = document.getElementById("seqContentWrapper");
474    var element = document.getElementById(sequencer.session.pipeline.uids[index]);
475    var otherElement = document.getElementById(sequencer.session.pipeline.uids[index+direction]);
476    // First buffer the two elements
477    var tempElement = element.cloneNode(true);
478    var tempOtherElement = otherElement.cloneNode(true);
479    var placeHolderElement = document.createElement("div");
480    placeHolderElement.id = "placeholder_element";
481    content.replaceChild(placeHolderElement, otherElement);
482    content.replaceChild(tempOtherElement, element);
483    content.replaceChild(tempElement, placeHolderElement);
484    //This should work.
485    // A-B     Start positions, backup to tA and tB
486    // A-X     Replace B with placeholder X
487    // B-X     Replace A with tB
488    // B-A     Replace placeholder X with tA.
489    // The two elements are now swapped.
490     
491    // Now swap the array entries.
492    sequencer.session.pipeline.uids[index] = sequencer.session.pipeline.uids.splice(index+direction, 1, sequencer.session.pipeline.uids[index])[0];
493    sequencer.session.pipeline.types[index] = sequencer.session.pipeline.types.splice(index+direction, 1, sequencer.session.pipeline.types[index])[0];
494     
495}
496
497function initEditor() {
498    // load settings fields first
499    var fContentType = document.getElementById("sContentTypeField");
500    var content = document.getElementById("seqContent");
501    //sequencer.settings.content.contentType = fContentType.value.toLowerCase();
502    sequencer.settings.content.contentType = "survey";
503   
504    var desiredWidth, desiredHeight;
505   
506    //Then select settings from a few presets
507    switch (sequencer.settings.content.contentType) {
508        case "session":
509            sequencer.settings.content.orientation = "h";
510            sequencer.settings.content.width = 800;
511            sequencer.settings.content.height = 125;
512            content.style.width = sequencer.settings.content.width+"px";
513            content.style.height = sequencer.settings.content.height+"px";
514            break;
515        case "survey":
516            sequencer.settings.content.orientation = "v";
517            sequencer.settings.content.width = 600;
518            sequencer.settings.content.height = "auto";
519            content.style.width = sequencer.settings.content.width+"px";
520            content.style.height = sequencer.settings.content.height+"px";
521            break;
522        default:
523            break;
524    }
525}
Note: See TracBrowser for help on using the repository browser.