source: Dev/branches/jQueryUI/client/js/old/questionEditorScripts.js @ 249

Last change on this file since 249 was 249, checked in by hendrikvanantwerpen, 13 years ago

This one's for Subversion, because it's so close...

First widget (stripped down sequencer).
Seperated client and server code in two direcotry trees.

File size: 21.1 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(uid) {
74        // Outer div
75        this.saved = false;
76        me.element = ce("div");
77        me.element.className = "smallFrame questionEditor";
78        me.element.id = sequencer.state.selectedObject.uid;
79        me.uid = uid;
80        // Header
81        var titleDiv = ce("div");
82        titleDiv.className = "smallTitle";
83        var numberDiv = ce("div");
84        numberDiv.className = "listNumber";
85        numberDiv.innerHTML = "4"; //TODO
86        var nameSpan = ce("span");
87        nameSpan.id = "qeTitleField";
88        nameSpan.innerHTML = "New question";
89        titleDiv.appendChild(numberDiv);
90        titleDiv.innerHTML += "Editing: ";
91        titleDiv.appendChild(nameSpan);
92        me.element.appendChild(titleDiv);
93   
94        //Content area
95        var contentDiv = ce("div");
96        contentDiv.className = "content";
97        var bodyText = createNewElement("textarea", null, "qeBodyTextField", "qeBodyTextField", null);
98        bodyText.value = "Question body text goes here";
99        contentDiv.appendChild(bodyText);
100       
101        // The dynamic questionParams div, where all the control elements and inputs will be located
102        var questionParams = ce("div");
103        me.paramsElement = questionParams;
104        questionParams.className = "questionParams";
105        questionParams.id = "qeQuestionParamsDiv";
106       
107        var basicContainer = ce("div");
108        basicContainer.id = "basicInputs";
109       
110        var qeCodeField = createNewElement("input", "text", "qeCodeField", "qeParamField", null);
111        var qeCodeField_lbl = createNewInputLabel("Question code:","qeCodeField", "l");
112        basicContainer.appendChild(qeCodeField_lbl);
113        basicContainer.appendChild(qeCodeField);
114   
115        var qeTypeField = createNewElement("select", null, "qeTypeField", "qeParamField", null);
116        var qeTypeField_lbl = createNewInputLabel("Answer type:","qeTypeField", "l");
117        basicContainer.appendChild(qeTypeField_lbl);
118        basicContainer.appendChild(qeTypeField);
119        questionParams.appendChild(basicContainer);
120        qeTypeField.addEventListener("change", function(){
121            //debugger;
122            me.selectAnswerType();
123        }, false);
124        // Add the select options. Do this in a block scope to prevent the o1 var from messing things up.
125        // Also helps in structuring code.
126        {
127            var o1 = ce("option");
128            o1.value = null;
129            o1.text = "";
130            qeTypeField.appendChild(o1);
131       
132            o1 = ce("option");
133            o1.value = "int";
134            o1.text = "Integer";
135            qeTypeField.appendChild(o1);
136       
137            o1 = ce("option");
138            o1.value = "scale";
139            o1.text = "Scale";
140            qeTypeField.appendChild(o1);
141       
142            o1 = ce("option");
143            o1.value = "choice";
144            o1.text = "Multiple choice";
145            qeTypeField.appendChild(o1);
146       
147            o1 = ce("option");
148            o1.value = "text";
149            o1.text = "Text";
150            qeTypeField.appendChild(o1);
151        }
152   
153        contentDiv.appendChild(questionParams);
154        me.element.appendChild(contentDiv);
155   
156        // Controls bar
157        var controlsDiv = ce("div");
158        controlsDiv.className = "controls";
159        var btnDiscard = createNewElement("input", "button", "btnDiscard", null, "Discard");
160        var btnSave = createNewElement("input", "button", "btnSave", null, "Save");
161        controlsDiv.appendChild(btnDiscard);
162        controlsDiv.appendChild(btnSave);
163        btnSave.addEventListener("click", function(){
164            me.save();
165        }, false);
166        btnDiscard.addEventListener("click", function(){
167            me.discard();
168        }, false);
169        me.element.appendChild(controlsDiv);
170        me.paramSets = new Array();
171        me.paramSets.push("basic");
172    }
173    this.save = function() {
174        var request = {
175            "title": ge("qeTitleField").innerHTML,
176            "code": ge("qeCodeField").value,
177            "description": ge("qeBodyTextField").value,
178            "uid": me.uid
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            console.log(result.responseText);
224            var response = JSON.parse(result.responseText);
225            console.log(response);
226            if (response.created == true) {
227                if (response.uid) {
228                    // Created a new question, add it to the sequencer content array and set it for an update
229                    sequencer.survey.questions.uids.push(response.uid);
230                    sequencer.survey.questions.upToDate.push(false);
231                }
232                else {
233                    alert("ERROR!");
234                }
235            } else {
236                if (response.uid){
237                    // Edited an existing question that was already in the sequencer content. Set it for an update
238                    sequencer.survey.questions.upToDate[sequencer.survey.questions.uids.indexOf(response.uid)] = false;
239                }
240                else {
241                    alert("ERROR!");
242                }
243            }
244           
245           
246            // Then remove the editor from the sequencer content, so that it can be replaced with the correct question view.
247            me.element.parentNode.removeChild(me.element);
248            sequencer.state.editing = false;
249            updateSequencer();
250        }, true);
251    }
252    this.discard = function() {
253        debugger;
254        me.element.parentNode.removeChild(me.element);
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(uid);
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.init(null);
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.