source: Dev/branches/jos-branch/js/questionEditorScripts.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: 21.2 KB
Line 
1var questionEditor = new QuestionEditor();
2
3function createNewElement(tag, type, id, cl, value) {
4    var newElement = document.createElement(tag);
5    if (type != undefined) newElement.type = type;
6    if (id != undefined) newElement.id = id;
7    if (cl != undefined) newElement.className = cl;
8    if (value != undefined) {
9        newElement.value = value;
10        newElement.text = value;
11    }
12    return newElement;
13}
14
15function createNewInputLabel(text, target, side) {
16    var newLabel = document.createElement("label");
17    if (target) newLabel.setAttribute("for",target);
18    newLabel.innerHTML = text;
19    if (side) newLabel.className = side;
20    return newLabel;
21}
22
23function QuestionEditor() {
24    // Properties   
25    this.uid = null;    // The uid of the question contained in this editor
26    this.saved = false; // Whether or not the question displayed in the editor has been saved already.
27    var me = this;      // Retarded self-reference because: Javascript
28    this.element = null;    // The parent div element containing the questionEditor
29    this.paramsElement = null;  // The parent parameters element where all the input sets will be located
30    this.paramSets = null;  // The currently enabled input sets to be displayed in the paramsElement.
31    //Currently only supports a single param set (based on answer type), but functionality will scale to multiple sets as needed.
32   
33    // Methods
34    // Basic functionality
35    this.setValues = function(arguments) {
36        debugger;
37        var question = JSON.parse(arguments)[0];
38        var qeTypeField = ge("qeTypeField");
39        ge("qeCodeField").value = question.code;
40        ge("qeBodyTextField").value = question.description;
41        switch (question.type.toLowerCase()) {
42            case "int":
43                // First make sure all the necessary fields are present
44                me.type_Integer();
45                // Then fill them in using the available data
46                qeTypeField.value = "int";
47                ge("qeMinValueField").value = question.minValue;
48                ge("qeMaxValueField").value = question.maxValue;
49                break;
50            case "scale":
51                me.type_Scale();
52                qeTypeField.value = "scale";
53                ge("qeNumChoicesField").value = question.numChoices;
54                ge("qeLegendsEnabledField").checked = question.legendsEnabled;
55                ge("qeLowerLegendField").value = question.lowerLegend;
56                ge("qeUpperLegendField").value = question.upperLegend;
57                break;
58            case "choice":
59                me.type_Choice();
60                qeTypeField.value = "choice";
61                ge("qeNumOptionsFIeld").value = question.numOptions;
62                ge("qeMultipleAnswersField").value = question.multipleAnswers;
63                // then some more to add textboxes (qeParamAnswerGroup) for all the possble choices.
64                // Maybe a central version that appends these groups/textinputs? Maybe not, though. Don't want stuff too abstracted...
65                break;
66            case "text":
67                me.type_Text();
68                qeTypeField.value = "text";
69                ge("qeTextMaxLengthField").value = question.maxTextLength;
70                break;
71               
72        }
73    }
74    this.init = function() {
75        // Outer div
76        this.saved = false;
77        me.element = ce("div");
78        me.element.className = "smallFrame questionEditor";
79        me.element.id = sequencer.state.selectedObject.uid;
80        me.uid = sequencer.state.selectedObject.uid;
81        // Header
82        var titleDiv = ce("div");
83        titleDiv.className = "smallTitle";
84        var numberDiv = ce("div");
85        numberDiv.className = "listNumber";
86        numberDiv.innerHTML = "4"; //TODO
87        var nameSpan = ce("span");
88        nameSpan.id = "qeTitleField";
89        nameSpan.innerHTML = "New question";
90        titleDiv.appendChild(numberDiv);
91        titleDiv.innerHTML += "Editing: ";
92        titleDiv.appendChild(nameSpan);
93        me.element.appendChild(titleDiv);
94   
95        //Content area
96        var contentDiv = ce("div");
97        contentDiv.className = "content";
98        var bodyText = createNewElement("textarea", null, "qeBodyTextField", "qeBodyTextField", null);
99        bodyText.value = "Question body text goes here";
100        contentDiv.appendChild(bodyText);
101       
102        // The dynamic questionParams div, where all the control elements and inputs will be located
103        var questionParams = ce("div");
104        me.paramsElement = questionParams;
105        questionParams.className = "questionParams";
106        questionParams.id = "qeQuestionParamsDiv";
107       
108        var basicContainer = ce("div");
109        basicContainer.id = "basicInputs";
110       
111        var qeCodeField = createNewElement("input", "text", "qeCodeField", "qeParamField", null);
112        var qeCodeField_lbl = createNewInputLabel("Question code:","qeCodeField", "l");
113        basicContainer.appendChild(qeCodeField_lbl);
114        basicContainer.appendChild(qeCodeField);
115   
116        var qeTypeField = createNewElement("select", null, "qeTypeField", "qeParamField", null);
117        var qeTypeField_lbl = createNewInputLabel("Answer type:","qeTypeField", "l");
118        basicContainer.appendChild(qeTypeField_lbl);
119        basicContainer.appendChild(qeTypeField);
120        questionParams.appendChild(basicContainer);
121        qeTypeField.addEventListener("change", function(){
122            //debugger;
123            me.selectAnswerType();
124        }, false);
125        // Add the select options. Do this in a block scope to prevent the o1 var from messing things up.
126        // Also helps in structuring code.
127        {
128            var o1 = ce("option");
129            o1.value = null;
130            o1.text = "";
131            qeTypeField.appendChild(o1);
132       
133            o1 = ce("option");
134            o1.value = "int";
135            o1.text = "Integer";
136            qeTypeField.appendChild(o1);
137       
138            o1 = ce("option");
139            o1.value = "scale";
140            o1.text = "Scale";
141            qeTypeField.appendChild(o1);
142       
143            o1 = ce("option");
144            o1.value = "choice";
145            o1.text = "Multiple choice";
146            qeTypeField.appendChild(o1);
147       
148            o1 = ce("option");
149            o1.value = "text";
150            o1.text = "Text";
151            qeTypeField.appendChild(o1);
152        }
153   
154        contentDiv.appendChild(questionParams);
155        me.element.appendChild(contentDiv);
156   
157        // Controls bar
158        var controlsDiv = ce("div");
159        controlsDiv.className = "controls";
160        var btnDiscard = createNewElement("input", "button", "btnDiscard", null, "Discard");
161        var btnSave = createNewElement("input", "button", "btnSave", null, "Save");
162        controlsDiv.appendChild(btnDiscard);
163        controlsDiv.appendChild(btnSave);
164        btnSave.addEventListener("click", function(){
165            me.save();
166        }, false);
167        btnDiscard.addEventListener("click", function(){
168            me.discard();
169        }, false);
170        me.element.appendChild(controlsDiv);
171        me.paramSets = new Array();
172        me.paramSets.push("basic");
173    }
174    this.save = function() {
175        var request = {
176            "title": ge("qeTitleField").innerHTML,
177            "code": ge("qeCodeField").value,
178            "description": ge("qeBodyTextField").value
179        }
180       
181        switch (ge("qeTypeField").value) {
182            case "int":
183                request.answerType = "int";
184                request.minValue = parseInt(ge("qeMinValueField").value);
185                if (request.minValue == "NaN") request.minValue = -1;   // Is this the correct way to do this?
186                request.maxValue = parseInt(ge("qeMaxValueField").value);
187                if (request.maxValue == "NaN") request.maxValue = -1;
188                // Include error checking and form validation!!
189                break;
190            case "scale":
191                request.answerType = "scale";
192                request.numChoices = parseInt(ge("qeNumChoicesField").value);
193                request.legendsEnabled = ge("qeLegendsEnabledField").checked;
194                request.lowerLegend = ge("qeLowerLegendField").value;
195                request.upperLegend = ge("qeUpperLegendField").value;
196                // Include error checking and form validation!!
197                break;
198            case "choice":
199                request.answerType = "choice";
200                request.multipleAnswers = ge("qeMultipleAnswersField").checked;
201                request.possibleAnswers = array();
202                var answerFieldset = ge("qeParamsAnswerFieldset");
203                var count = ge("qeNumOptionsField").value;
204               
205                for (var i = 0; i < count; i++) {
206                    var el = ge("qeAnswerField"+i);
207                    request.possibleAnswers.push(el.value);
208                }
209                // Include error checking and form validation!!
210                break;
211            case "text":
212                request.answerType = "text";
213                request.maxTextLength = ge("qeTextMaxLength").value;
214                // Include error checking and form validation!!
215                break;
216        }
217       
218        requestString = "args="+JSON.stringify(request);
219       
220        newAjaxRequest(requestString, "setQuestion.php", function(result){
221            // Then add the returned uid, if existing, to the sequencer.survey.questions array and set it for update
222            debugger;
223            var response = JSON.parse(result.responseText);
224            console.log(response);
225            if (response.created == true) {
226                if (response.uid) {
227                    // Created a new question, add it to the sequencer content array and set it for an update
228                    sequencer.survey.questions.uids.push(response.uid);
229                    sequencer.survey.questions.upToDate.push(false);
230                }
231                else {
232                    alert("ERROR!");
233                }
234            } else {
235                if (response.uid){
236                    // Edited an existing question that was already in the sequencer content. Set it for an update
237                    sequencer.survey.questions.upToDate[sequencer.survey.questions.uids.indexOf(response.uid)] = false;
238                }
239                else {
240                    alert("ERROR!");
241                }
242            }
243           
244           
245            // Then remove the editor from the sequencer content, so that it can be replaced with the correct question view.
246            me.element.parentNode.removeChild(me.element);
247            sequencer.state.editing = false;
248            updateSequencer();
249        }, true);
250    }
251    this.discard = function() {
252            debugger;
253            me.element.parentNode.removeChild(me.element);
254            me.init();
255            sequencer.state.loaded = true;
256            sequencer.state.editing = false;
257            sequencer.survey.questions.upToDate[sequencer.survey.questions.uids.indexOf(me.uid)] = false;
258            updateSequencer();
259       
260    }
261    this.reset = function() {
262        me.init();
263    }
264   
265    // Updating input fields
266    this.selectAnswerType = function () {
267        // Switch statement, call this.type_x where x is the relevant answer type.
268        // For all this.type_X funtions:
269        // Use the createNewElement and createNewInputLabel methods to add controls or input fields.
270        // Input field convention:  class = questionParamField, id = qTypeField / qScaleSize, etc...
271        // Input label class: "l" or "r" depending on alignment with respect to parent ("for") input element.
272        // Important: use the this.paramsElement to access the questionParams div!
273        // To fully make use of this class-based approach, the editor should be added to a global variable. This global variable should be removed on page unload!
274   
275        var type = ge("qeTypeField").value;
276        switch (type) {
277            case "int":
278                me.type_Integer();
279                break;
280            case "scale":
281                me.type_Scale();
282                break;
283            case "choice":
284                me.type_Choice();
285                break;
286            case "text":
287                me.type_Text();
288                break;
289            default:
290                //Do nothing
291                break;
292               
293        }
294   
295    }
296    this.checkInputSets = function () {
297        // Loop through all input containers in the paramsField
298        for (var n = 0; n < me.paramsElement.childNodes.length; n++) {
299            if (me.paramsElement.childNodes[n].id == "basicInputs") continue;
300            // Check if the id (inputSet) is currently in paramSets
301            if (me.paramSets.indexOf(me.paramsElement.childNodes[n].id) < 0) {
302                me.paramsElement.childNodes[n].parentNode.removeChild(me.paramsElement.childNodes[n]);
303                n--;
304            }
305           
306        }
307    }
308   
309    this.type_Integer = function () {
310        if (me.paramSets.indexOf("int_basic") < 0) {
311            me.paramSets = new Array("int_basic");
312        }
313        else return;
314       
315        me.checkInputSets();
316       
317        var container = ce("div");
318        container.id = "int_basic";
319
320        var qeMinValueField = createNewElement("input", "text", "qeMinValueField", "qeParamField", null);
321        var qeMinValueField_lbl = createNewInputLabel("Minimum value: ", "qeMinValueField", "l");
322        var qeMaxValueField = createNewElement("input", "text", "qeMaxValueField", "qeParamField", null);
323        var qeMaxValueField_lbl = createNewInputLabel("Maximum value: ", "qeMaxValueField", "l");
324       
325        container.appendChild(qeMinValueField_lbl);
326        container.appendChild(qeMinValueField);
327        container.appendChild(qeMaxValueField_lbl);
328        container.appendChild(qeMaxValueField);
329        me.paramsElement.appendChild(container);
330       
331    }
332    this.type_Scale = function () {
333        if (me.paramSets.indexOf("scale_basic") < 0) {
334            me.paramSets = new Array("scale_basic");
335        }
336        else return;
337        // Clear any input sets that should not be there
338        me.checkInputSets();
339       
340        var container = ce("div");
341        container.id = "scale_basic";
342       
343        // Number of choices SELECT
344        var numChoicesField = createNewElement("select", null, "qeNumChoicesField", "qeParamField", null);
345        var numChoicesField_lbl = createNewInputLabel("Number of choices", "qeNumChoicesField", "l");
346        // SELECT options
347        for (var n = 2; n < 11; n++) {
348            var o = ce("option");
349            o.value = n;
350            o.text = n;
351            numChoicesField.appendChild(o);
352        }
353        container.appendChild(numChoicesField_lbl);
354        container.appendChild(numChoicesField);
355       
356        // Scale labels CHECKBOX and TEXTs
357        var legendsEnabledField = createNewElement("input", "checkbox", "qeLegendsEnabledField", "qeParamField", null);
358        var legendsEnabledField_lbl = createNewInputLabel("Enable legends", "qeLegendsEnabledField", "l");
359        container.appendChild(legendsEnabledField_lbl);
360        container.appendChild(legendsEnabledField);
361       
362        var upperLegendText = createNewElement("input", "text", "qeUpperLegendField", "qeParamField", null);
363        var lowerLegendText = createNewElement("input", "text", "qeLowerLegendField", "qeParamField", null);
364        var upperLegendText_lbl = createNewInputLabel("Upper legend", "qeUpperLegendField", "l");
365        var lowerLegendText_lbl = createNewInputLabel("Lower legend", "qeLowerLegendField", "l");
366        container.appendChild(lowerLegendText_lbl);
367        container.appendChild(lowerLegendText);
368        container.appendChild(upperLegendText_lbl);
369        container.appendChild(upperLegendText);
370       
371        me.paramsElement.appendChild(container);
372    }
373    this.type_Text = function () {
374        if (me.paramSets.indexOf("text_basic") < 0) {
375            me.paramSets = new Array("text_basic");
376        }
377        else return;
378       
379        me.checkInputSets();
380       
381        var container = ce("div");
382        container.id="text_basic";
383    }
384    this.type_Choice = function() {
385        //debugger;
386        if (me.paramSets.indexOf("choice_basic") < 0) {
387            me.paramSets = new Array("choice_basic");
388        }
389        else return;
390       
391        me.checkInputSets();
392       
393        var container = ce("div");
394        container.id = "choice_basic";
395        // num options SELECT
396        var numOptionsSelect = createNewElement("select", null, "qeNumOptionsField", "qeParamField", null);
397        var numOptionsSelect_lbl = createNewInputLabel("Number of options", "qeNumOptionsField", "l");
398        for (var n = 2; n < 11; n++) {
399            var o = ce("option");
400            o.value = n;
401            o.text = n;
402            numOptionsSelect.appendChild(o);
403        }
404        container.appendChild(numOptionsSelect_lbl);
405        container.appendChild(numOptionsSelect);
406        numOptionsSelect.addEventListener("change", me.type_Choice_CheckAnswerFields, true);
407       
408        var allowMultiple = createNewElement("input", "checkbox", "qeMultipleAnswersField", "qeParamField", null);
409        var allowMultiple_lbl = createNewInputLabel("Allow multiple answers", "qeMultipleAnswersField", "l");
410        container.appendChild(allowMultiple_lbl);
411        container.appendChild(allowMultiple);
412       
413        var answersField = ce("div");
414        answersField.className = "qeParamFieldset";
415        answersField.id = "qeParamsAnswerFieldset"
416        container.appendChild(answersField);
417                       
418        me.paramsElement.appendChild(container);
419        me.type_Choice_CheckAnswerFields();
420    }
421    this.type_Choice_CheckAnswerFields = function() {
422        var container = ge("qeParamsAnswerFieldset");
423        var numAnswers = parseInt(document.getElementById("qeNumOptionsField").value, 10);
424        var numAnswerFields = 0;
425        {
426            for (var i = container.childNodes.length-1; i >= 0; i--) {
427                if (container.childNodes[i].className = "qeChoiceAnswerGroup") {
428                    numAnswerFields++;
429                }
430            }
431           
432        }
433       
434        // If there already are the correct number of answer fields, exit the function
435        if (numAnswers == numAnswerFields) return;
436        else if (numAnswers > numAnswerFields) {
437            // Extra inputs need to be added
438            var n = numAnswers - numAnswerFields;
439            for (var x = 1; x < n+1; x++) {
440                var group = ce("div");
441                group.className = "qeChoiceAnswerGroup";
442                var field = createNewElement("input", "text", "qeAnswerField"+(numAnswerFields+x), "qeParamField", null);
443                var field_lbl = createNewInputLabel((numAnswerFields+x), "qeAnswerField"+(numAnswerFields+x), "l");
444               
445                group.appendChild(field_lbl);
446                group.appendChild(field);
447                container.appendChild(group);
448            }
449        }
450        else if (numAnswers < numAnswerFields) {
451            // There are too many inputs and some need to be removed. Start at the end!
452            // TODO: This is SO inefficient. There has to be a better way, perhaps adding elements to an array?
453            // TODO: Another approach would be to use the previousSibling property as a way to prevent having to loop through the entire container tree every time an object needs to be removed.
454            var n = numAnswerFields-numAnswers;
455            for (var x = 0; x < n; x++) {
456                for (var y = container.childNodes.length-1; y >= 0; y--) {
457                    if (container.childNodes[y].className == "qeChoiceAnswerGroup") {
458                        container.removeChild(container.childNodes[y]);
459                        break;
460                    }
461                }
462            }
463        }
464    }
465     
466    // Editing
467    this.editQuestion = function(uid) {
468        debugger;
469        if (sequencer.state.editing == true) return;
470        if (sequencer.state.loaded == false) return;
471        sequencer.state.editing = true;
472        sequencer.state.loaded = false;
473       
474        var request = new Array({
475            type: "Question",
476            uid: uid
477        });
478        me.init();
479        var oldElement = ge(uid);
480        if (oldElement) {
481            // There really should be.... I don't know why I am doing this check...
482            oldElement.parentNode.replaceChild(me.element, oldElement);
483        }
484        var requestString = "args="+JSON.stringify(request);
485        newAjaxRequest(requestString, "getObject.php", function(result){
486            // Once results are in
487            me.setValues(result.responseText);
488            sequencer.state.loaded = true;
489        }, true);
490    }
491    this.createNewQuestion = function() {
492        if (sequencer.state.editing == true) return;
493        if (sequencer.state.loading == false) return;
494        sequencer.state.editing = true;
495   
496        me.reset();
497        var container = ge("seqContentWrapper");
498        container.appendChild(me.element);
499    }
500}
501
502// IT LIIIIIIVESSSSS
503// TODO: Add database fields for all the necessary question parameters. Maybe only one question parameter property that holds all the settings, then read it out in javascript?
504// Why have a question Description? Is this even necessary?
505// Needed properties:
506// Also not exactly sure what "question->category" is for. Is this one of those questionSet things that Julia was talking about?
507//
Note: See TracBrowser for help on using the repository browser.