source: Dev/branches/jos-branch/js/questionEditorScripts.js @ 237

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