var questionEditor = new QuestionEditor(); function createNewElement(tag, type, id, cl, value) { var newElement = document.createElement(tag); if (type != undefined) newElement.type = type; if (id != undefined) newElement.id = id; if (cl != undefined) newElement.className = cl; if (value != undefined) { newElement.value = value; newElement.text = value; } return newElement; } function createNewInputLabel(text, target, side) { var newLabel = document.createElement("label"); if (target) newLabel.setAttribute("for",target); newLabel.innerHTML = text; if (side) newLabel.className = side; return newLabel; } function updateIdentifier() { var identField = document.getElementById("questionIdentifierField"); if (identField.value == undefined && identField.value == "") { return; } var headerField = document.getElementById("header_identifier"); headerField.innerHTML = identField.value; } ////////////////////////////////////////// /* QUESTION EDITOR CLASS BASED APPROACH */ ////////////////////////////////////////// function QuestionEditor() { // Properties this.uid = null; // The uid of the question contained in this editor var me = this; // Retarded self-reference because: Javascript this.element = null; // The parent div element containing the questionEditor this.paramsElement = null; // The parent parameters element where all the input sets will be located this.paramSets = null; // The currently enabled input sets to be displayed in the paramsElement. //Currently only supports a single param set (based on answer type), but functionality will scale to multiple sets as needed. // Methods // Basic functionality this.setValues = function(arguments) { var question = JSON.parse(arguments); } this.init = function() { // Outer div me.element = ce("div"); me.element.className = "smallFrame questionEditor"; me.element.id = sequencer.state.selectedObject.uid; me.uid = sequencer.state.selectedObject.uid; // Header var titleDiv = ce("div"); titleDiv.className = "smallTitle"; var numberDiv = ce("div"); numberDiv.className = "listNumber"; numberDiv.innerHTML = "4"; //TODO var nameSpan = ce("span"); nameSpan.id = "qeTitleField"; nameSpan.innerHTML = "New question"; titleDiv.appendChild(numberDiv); titleDiv.innerHTML += "Editing: "; titleDiv.appendChild(nameSpan); me.element.appendChild(titleDiv); //Content area var contentDiv = ce("div"); contentDiv.className = "content"; var bodyText = createNewElement("textarea", null, "qeBodyTextField", "qeBodyTextField", null); bodyText.value = "Question body text goes here"; contentDiv.appendChild(bodyText); // The dynamic questionParams div, where all the control elements and inputs will be located var questionParams = ce("div"); me.paramsElement = questionParams; questionParams.className = "questionParams"; questionParams.id = "qeQuestionParamsDiv"; var basicContainer = ce("div"); basicContainer.id = "basicInputs"; var qeCodeField = createNewElement("input", "text", "qeCodeField", "qeParamField", null); var qeCodeField_lbl = createNewInputLabel("Question code:","qeCodeField", "l"); basicContainer.appendChild(qeCodeField_lbl); basicContainer.appendChild(qeCodeField); var qeTypeField = createNewElement("select", null, "qeTypeField", "qeParamField", null); var qeTypeField_lbl = createNewInputLabel("Answer type:","qeTypeField", "l"); basicContainer.appendChild(qeTypeField_lbl); basicContainer.appendChild(qeTypeField); questionParams.appendChild(basicContainer); qeTypeField.addEventListener("change", function(){ //debugger; me.selectAnswerType(); }, false); // Add the select options. Do this in a block scope to prevent the o1 var from messing things up. // Also helps in structuring code. { var o1 = ce("option"); o1.value = null; o1.text = ""; qeTypeField.appendChild(o1); o1 = ce("option"); o1.value = "int"; o1.text = "Integer"; qeTypeField.appendChild(o1); o1 = ce("option"); o1.value = "scale"; o1.text = "Scale"; qeTypeField.appendChild(o1); o1 = ce("option"); o1.value = "choice"; o1.text = "Multiple choice"; qeTypeField.appendChild(o1); o1 = ce("option"); o1.value = "text"; o1.text = "Text"; qeTypeField.appendChild(o1); } contentDiv.appendChild(questionParams); me.element.appendChild(contentDiv); // Controls bar var controlsDiv = ce("div"); controlsDiv.className = "controls"; var btnDiscard = createNewElement("input", "button", "btnDiscard", null, "Discard"); var btnSave = createNewElement("input", "button", "btnSave", null, "Save"); controlsDiv.appendChild(btnDiscard); controlsDiv.appendChild(btnSave); btnSave.addEventListener("click", function(){ me.save(); }, false); btnDiscard.addEventListener("click", function(){ me.discard(); }, false); me.element.appendChild(controlsDiv); me.paramSets = new Array(); me.paramSets.push("basic"); } this.save = function() { var request = { "title": ge("qeTitleField").value, "type": ge("qeTypeField").value } switch (ge("qeTypeField").value) { case "int": request.answerType = "int"; request.minValue = parseInt(ge("qeMinValueField").value); request.maxValue = parseInt(ge("qeMaxValueField").value); break; case "scale": request.answerType = "scale"; request.numChoices = parseInt(ge("qeNumChoicesField").value); request.legendsEnabled = ge("qeLegendsEnabledField").checked; request.lowerLegend = ge("qeLowerLegendField").value; request.upperLegend = ge("qeUpperLegendField").value; break; case "choice": request.answerType = "choice"; break; case "text": request.answerType = "text"; break; } newAjaxRequest(requestString, "createObject.php", function(result){ // Display a success message, or throw an error. }, true); } this.reset = function() { me.init(); } // Updating input fields this.selectAnswerType = function () { // Switch statement, call this.type_x where x is the relevant answer type. // For all this.type_X funtions: // Use the createNewElement and createNewInputLabel methods to add controls or input fields. // Input field convention: class = questionParamField, id = qTypeField / qScaleSize, etc... // Input label class: "l" or "r" depending on alignment with respect to parent ("for") input element. // Important: use the this.paramsElement to access the questionParams div! // 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! var type = ge("qeTypeField").value; switch (type) { case "int": me.type_Integer(); break; case "scale": me.type_Scale(); break; case "choice": me.type_Choice(); break; case "text": me.type_Text(); break; default: //Do nothing break; } } this.checkInputSets = function () { // Loop through all input containers in the paramsField for (var n = 0; n < me.paramsElement.childNodes.length; n++) { if (me.paramsElement.childNodes[n].id == "basicInputs") continue; // Check if the id (inputSet) is currently in paramSets if (me.paramSets.indexOf(me.paramsElement.childNodes[n].id) < 0) { me.paramsElement.childNodes[n].parentNode.removeChild(me.paramsElement.childNodes[n]); n--; } } } this.type_Integer = function () { if (me.paramSets.indexOf("int_basic") < 0) { me.paramSets = new Array("int_basic"); } else return; me.checkInputSets(); var container = ce("div"); container.id = "int_basic"; var qeMinValueField = createNewElement("input", "text", "qeMinValueField", "qeParamField", null); var qeMinValueField_lbl = createNewInputLabel("Minimum value: ", "qeMinValueField", "l"); var qeMaxValueField = createNewElement("input", "text", "qeMaxValueField", "qeParamField", null); var qeMaxValueField_lbl = createNewInputLabel("Maximum value: ", "qeMaxValueField", "l"); container.appendChild(qeMinValueField_lbl); container.appendChild(qeMinValueField); container.appendChild(qeMaxValueField_lbl); container.appendChild(qeMaxValueField); me.paramsElement.appendChild(container); } this.type_Scale = function () { if (me.paramSets.indexOf("scale_basic") < 0) { me.paramSets = new Array("scale_basic"); } else return; // Clear any input sets that should not be there me.checkInputSets(); var container = ce("div"); container.id = "scale_basic"; // Number of choices SELECT var numChoicesField = createNewElement("select", null, "qeNumChoicesField", "qeParamField", null); var numChoicesField_lbl = createNewInputLabel("Number of choices", "qeNumChoicesField", "l"); // SELECT options for (var n = 2; n < 11; n++) { var o = ce("option"); o.value = n; o.text = n; numChoicesField.appendChild(o); } container.appendChild(numChoicesField_lbl); container.appendChild(numChoicesField); // Scale labels CHECKBOX and TEXTs var legendsEnabledField = createNewElement("input", "checkbox", "qeLegendsEnabledField", "qeParamField", null); var legendsEnabledField_lbl = createNewInputLabel("Enable legends", "qeLegendsEnabledField", "l"); container.appendChild(legendsEnabledField_lbl); container.appendChild(legendsEnabledField); var upperLegendText = createNewElement("input", "text", "qeUpperLegendField", "qeParamField", null); var lowerLegendText = createNewElement("input", "text", "qeLowerLegendField", "qeParamField", null); var upperLegendText_lbl = createNewInputLabel("Upper legend", "qeUpperLegendField", "l"); var lowerLegendText_lbl = createNewInputLabel("Lower legend", "qeLowerLegendField", "l"); container.appendChild(lowerLegendText_lbl); container.appendChild(lowerLegendText); container.appendChild(upperLegendText_lbl); container.appendChild(upperLegendText); me.paramsElement.appendChild(container); } this.type_Text = function () { if (me.paramSets.indexOf("text_basic") < 0) { me.paramSets = new Array("text_basic"); } else return; me.checkInputSets(); var container = ce("div"); container.id="text_basic"; } this.type_Choice = function() { //debugger; if (me.paramSets.indexOf("choice_basic") < 0) { me.paramSets = new Array("choice_basic"); } else return; me.checkInputSets(); var container = ce("div"); container.id = "choice_basic"; // num options SELECT var numOptionsSelect = createNewElement("select", null, "qeNumOptionsField", "qeParamField", null); var numOptionsSelect_lbl = createNewInputLabel("Number of options", "qeNumOptionsField", "l"); for (var n = 2; n < 11; n++) { var o = ce("option"); o.value = n; o.text = n; numOptionsSelect.appendChild(o); } container.appendChild(numOptionsSelect_lbl); container.appendChild(numOptionsSelect); numOptionsSelect.addEventListener("change", me.type_Choice_CheckAnswerFields, true); var allowMultiple = createNewElement("input", "checkbox", "qeMultipleAnswersField", "qeParamField", null); var allowMultiple_lbl = createNewInputLabel("Allow multiple answers", "qeMultipleAnswersField", "l"); container.appendChild(allowMultiple_lbl); container.appendChild(allowMultiple); var answersField = ce("div"); answersField.className = "qeParamFieldset"; answersField.id = "qeParamsAnswerFieldset" container.appendChild(answersField); me.paramsElement.appendChild(container); me.type_Choice_CheckAnswerFields(); } this.type_Choice_CheckAnswerFields = function() { debugger; var container = ge("qeParamsAnswerFieldset"); var numAnswers = parseInt(document.getElementById("qeNumOptionsField").value, 10); var numAnswerFields = 0; { for (var i = container.childNodes.length-1; i >= 0; i--) { if (container.childNodes[i].className = "qeChoiceAnswerGroup") { numAnswerFields++; } } } // If there already are the correct number of answer fields, exit the function if (numAnswers == numAnswerFields) return; else if (numAnswers > numAnswerFields) { // Extra inputs need to be added var n = numAnswers - numAnswerFields; for (var x = 0; x < n; x++) { var group = ce("div"); group.className = "qeChoiceAnswerGroup"; var field = createNewElement("input", "text", "qeAnswerField"+(numAnswerFields+x), "qeParamField", null); var field_lbl = createNewInputLabel((numAnswerFields+x), "qeAnswerField"+(numAnswerFields+x), "l"); group.appendChild(field_lbl); group.appendChild(field); container.appendChild(group); } } else if (numAnswers < numAnswerFields) { // There are too many inputs and some need to be removed. Start at the end! // TODO: This is SO inefficient. There has to be a better way, perhaps adding elements to an array? // 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. var n = numAnswerFields-numAnswers; for (var x = 0; x < n; x++) { for (var y = container.childNodes.length-1; y >= 0; y--) { if (container.childNodes[y].className == "qeChoiceAnswerGroup") { container.removeChild(container.childNodes[y]); break; } } } } } // Editing this.editQuestion = function(uid) { if (sequencer.state.editing == true) return; if (sequencer.state.loaded == false) return; sequencer.state.editing = true; var request = { "type": "Question", "uid": uid } var requestString = "args="+JSON.stringify(request); sequencer.state.loaded = false; newAjaxRequest(requestString, getObject.php, function(result){ // Once results are in questionEditor.setValues(result.responseText); sequencer.state.loaded = true; }, true); } this.createNewQuestion = function() { if (sequencer.state.editing == true) return; if (sequencer.state.loading == true) return; sequencer.state.editing = true; me.reset(); var container = ge("seqContentWrapper"); container.appendChild(me.element); } }