/* * To change this template, choose Tools | Templates * and open the template in the editor. */ var sequencer = { // GLOBAL VAR TO STORE SEQUENCER SETTINGS! uid: "", // The unique id of this sequencer (not DB related!). This will help to avoid global var conflicts. Assign this randomly! (STRING) session: { // Properties of the currently loaded session title: "", // Title or name (STRING) uid: "", // Database UID of the current session (STRING) pipeline: { // Pipeline uids: [], // Uids of objects in pipeline (STRING) types: [], // Types of objects in pipeline (STRING) upToDate: [] // Whether or not object displays are up to date (BOOL) } }, 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!] title: "", // Title or the name of the survey uid: "", // Uid of the survey questions: { // Properties of the questions contained within this survey uids: [], // An array of uids of the questions, in the order that they appear in the survey upToDate: [] // Whether or not a certain question needs an update } }, state: { // Operating state of the sequencer 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. numSteps: 0, // Number of steps currently drawn in the editor (not necessarily same as number of steps in pipeline!) (INTEGER) loaded: false, // Whether or not the sequencer content has been updated for the first time (BOOL) selectedStep: { // Properties of the currently selected step uid: "", // UID of this step (STRING) index: null // The 'index' of this step in the current sequencer view (NOT the pipeline!) (INTEGER) } }, settings: { // Various settings to determine the workings of the sequencer content: { // Properties related to the content view of the sequencer contentType: "session", // Type of the loaded parent object (STRING) width: null, // Width of the viewing area (INTEGER) height: null, // Height of the viewing area (INTEGER) maxObjects: null, // The maximum number of content elements to be displayed at once time (INTEGER) orientation: "horizontal" // Whether the editor should be a vertical or horizontal editor (STRING) }, 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 } } function SubmitToolbox(type) { deselectStep(); var c = "objectToCreate="+type; var u = "createObject.php"; newAjaxRequest(c, u, function(result) { sequencer.session.pipeline.uids.push(removeNL(result.responseText)); sequencer.session.pipeline.types.push(type); sequencer.session.pipeline.upToDate.push(false); updateSequencer(); }, true); } function clickStep(uid) { if (uid == sequencer.state.selectedStep.uid) { // user deselected a currently selected step. deselectStep(); } else { if (sequencer.state.selectedStep.uid != undefined && sequencer.state.selectedStep.uid != "") { deselectStep(); selectStep(uid); } else { selectStep(uid); } } } function selectStep(uid) { var element = document.getElementById(uid); if (element) { addClass(element, "selected"); var type = sequencer.session.pipeline.types[sequencer.session.pipeline.uids.indexOf(uid)]; ajaxInfoRequest(uid, document.getElementById("infoPanelContent"), type); sequencer.state.selectedStep.uid = uid; sequencer.state.selectedStep.index = null; //Don't know how to do this yet. } } function deleteStep() { // check if a step is selected if (sequencer.state.selectedStep.uid == null) { return; } var uid = sequencer.state.selectedStep.uid; // deselect the step to reset the info panel and selection code deselectStep(); // splice the step's data from the pipeline var index = sequencer.session.pipeline.uids.indexOf(uid); if (index >= 0 && index < sequencer.session.pipeline.uids.length) { sequencer.session.pipeline.uids.splice(index, 1); sequencer.session.pipeline.types.splice(index, 1); sequencer.session.pipeline.upToDate.splice(index, 1); // Then delete the step visually var element = document.getElementById(uid); var divider; if (!element.nextSibling) { // the element is at the end of the pipeline // therefore we remove the previous divider. // Note: it could also be the only element left in the pipeline! divider = (element.previousSibling) ? element.previousSibling : false; if (divider != false) { divider.parentNode.removeChild(divider); } } else { // the element is at any position except the last, therefore we remove the next divider divider = (element.nextSibling) ? element.nextSibling : false; if (divider != false) { divider.parentNode.removeChild(divider); } } // Finally, remove the element itself. element.parentNode.removeChild(element); sequencer.state.numSteps--; } } function deselectStep() { if (!sequencer.state.selectedStep.uid) return; var element = document.getElementById(sequencer.state.selectedStep.uid); removeClass(element, "selected"); sequencer.state.selectedStep.uid = null; sequencer.state.selectedStep.index = null; var infoPanel = document.getElementById("infoPanelContent"); while (infoPanel.firstChild) infoPanel.removeChild(infoPanel.firstChild); } function ajaxInfoRequest(uid, el, type) { var c = "uid="+uid; c += "&type="+type; var u = "getInfo.php"; newAjaxRequest(c, u, function(result) { el.innerHTML = result.responseText; }, true); } function updateSequencer() { /* * Description: * This function updates the visual elements in the sequencer content view to match the current state of the sequencer.session.pipeline property. * It queries the database for object properties via AJAX (returnStep/Display/.php), then inserts divider div's in between where needed. */ var content = document.getElementById("seqContentWrapper"); var requestString, needsUpdating; var args; 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 // Stop running this function if the pipeline does not contain any elements to draw if (sequencer.session.pipeline.uids.length == 0 || !sequencer.session.pipeline.uids.length) return; // First clear the entire content wrapper, just for safety and in case efficientUpdating is off while (content.firstChild) { content.removeChild(content.firstChild); } args = []; needsUpdating = []; for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) { args.push({ uid: sequencer.session.pipeline.uids[i], type: sequencer.session.pipeline.types[i] }); needsUpdating.push(new Array(i, sequencer.session.pipeline.uids[i], sequencer.session.pipeline.types[i])); } requestString = "args="+JSON.stringify(args); newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){ content.removeChild(loadingGif); insertNewObjects(result.responseText, needsUpdating); }, true); sequencer.state.loaded = true; var loadingGif = document.createElement("div"); loadingGif.innerHTML = ""; content.appendChild(loadingGif); } else { // This means that one or more steps are being added, not an entire pipeline's worth of them needsUpdating = new Array(); args = []; // Add steps that need updating to the needsUpdating array (index, uid, type). for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) { if (sequencer.session.pipeline.upToDate[i] == true) continue; needsUpdating.push(new Array(i, sequencer.session.pipeline.uids[i], sequencer.session.pipeline.types[i])); args.push({ uid: sequencer.session.pipeline.uids[i], type: sequencer.session.pipeline.types[i] }); } requestString = "args="+JSON.stringify(args); newAjaxRequest(requestString, "returnObjectDisplay.php", function(result){ insertNewObjects(result.responseText, needsUpdating); }, true); // Optional bit with the loading GIF for (var i = 0; i < needsUpdating.length; i++) { var loadingDiv = document.createElement("div"); loadingDiv.className = "displayStep loading"; loadingDiv.innerHTML = ""; if (needsUpdating[i][0] > sequencer.state.numSteps-1) { content.appendChild(loadingDiv); sequencer.state.numSteps++; } else { content.replaceChild(loadingDiv, content.childNodes[i][0]*2); } } updateDividers(); // End optional } console.log(sequencer); } function loadSequencer() { /* * Description: * Load data from hidden fields (put there by PHP), store them in the global var "sequencer" (as well as several initialization properties), * then remove the hidden fields from the HTML document tree. */ // Load hidden fields and set required properties in global object var. try { // settings fields first initEditor(); // Content-related fields next var fPipelineString = document.getElementById("pipelineStringField"); var fPipelineTypes = document.getElementById("pipelineTypeField"); var fSessionUid = document.getElementById("sessionField"); var fSessionTitle = document.getElementById("sessionTitleField"); sequencer.session.title = fSessionTitle.value; sequencer.session.uid = fSessionUid.value; sequencer.session.pipeline.uids = stringToArray(fPipelineString.value, ","); sequencer.session.pipeline.types = stringToArray(fPipelineTypes.value, ","); sequencer.session.pipeline.upToDate = new Array(); for (var i = 0; i < sequencer.session.pipeline.uids.length; i++) { sequencer.session.pipeline.upToDate.push(true); } sequencer.state.numSteps = 0; sequencer.state.loaded = false; sequencer.settings.content.orientation = "h"; sequencer.settings.efficientUpdating = true; } catch (e) { // Alert developer of any errors while setting these variables for (error in e) alert(error.message); } // Then remove the hidden fields from the HTML document var hiddenInputs = document.getElementById("hiddenInputs"); hiddenInputs.parentNode.removeChild(hiddenInputs); // finally, run updateSequencer to refresh the visual display of the pipeline updateSequencer(); } function insertNewObjects(responseText, needsUpdating) { var response = JSON.parse(responseText); // 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. switch (response[0].type.toLowerCase()) { case "survey": case "application": case "dashboard": insertNewSteps(response, needsUpdating); break; case "question": insertNewQuestions(response, needsUpdating); break; default: break; } } function insertNewSteps(response, needsUpdating) { /* * This is a test function displaying how to handle the visual object representation in solely javascript. * Communication of relevant variables between PHP and JS happens in JSON format. * PHP returns a JSON array of objects to be created by JS * JS then loops through this array and creates DIVS to be inserted into the sequencer. * These are inserted at the position needsUpdating gives us. */ var content = document.getElementById("seqContentWrapper"); // Remove optional loading images for (var i = 0; i < content.childNodes.length; i++) { if (hasClass(content.childNodes[i], "loading")) { content.removeChild(content.childNodes[i]); } } // End optional for (var i = 0; i < response.length; i++) { var tempDiv = document.createElement("div"); tempDiv.id = response[i].uid; tempDiv.className = "displayStep"; var divImageContainer = document.createElement("div"); divImageContainer.className = "displayStepIcon"; divImageContainer.addEventListener("click", function(){ clickStep(this.parentNode.id); }, false); var divImage = document.createElement("img"); divImage.src = "images/icons/"+response[i].type.toLowerCase()+".png"; divImageContainer.appendChild(divImage); tempDiv.appendChild(divImageContainer); var divLabel = document.createElement("p"); divLabel.innerHTML = response[i].title; tempDiv.appendChild(divLabel); // 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. for (var j = needsUpdating.length-1; j >= 0; j--) { if (needsUpdating[j][1] != response[i].uid) continue; if (needsUpdating[j][0] > sequencer.state.numSteps-1) { content.appendChild(tempDiv); } else { content.replaceChild(tempDiv, content.childNodes[j][0]*2); } sequencer.session.pipeline.upToDate[needsUpdating[j][0]] = true; } } updateDividers(); } function insertNewQuestions(response, needsUpdating) { var content = document.getElementById("seqcContentWrapper"); // Loop through returned question objects for (var i = 0; i < response.length; i++) { // Define the outer frame var frameDiv = document.createElement("div"); frameDiv.classname = "smallFrame question"; frameDiv.id = response[i].uid; var titleDiv = document.createElement("div"); titleDiv.className = "smallTitle"; var numberDiv = document.createElement("div"); numberDiv.className = "listNumber"; numberDiv.innerHTML = i.toString(); titleDiv.appendChild(numberDiv); titleDiv.innerHTML += response[i].uid; frameDiv.appendChild(titleDiv); // The frame now has a header bar // On to the content frame // Will use new "ce();" function, shorthand for document.createElement var contentDiv = ce("div"); contentDiv.className = "content"; var questionBody = ce("p"); questionBody.innerHTML = response[i].description; var questionParamsDiv = ce("div"); questionParamsDiv.className = "questionParamsView"; questionParamsDiv.innerHTML = "Object type: "+response[i].type; contentDiv.appendChild(questionBody); contentDiv.appendChild(questionParamsDiv); frameDiv.appendChild(contentDiv); // And finally the controls div var controlsDiv = ce("div"); controlsDiv.className = "controls"; var editButton = ce("input"); var removeButton = ce("input"); editButton.value = "Edit"; removeButton.value = "Remove"; editButton.className = "smallButton"; removeButton.className = "smallButton"; editButton.addEventListener("click", function(e){ alert('Editing not yet supported!'); }, false); removeButton.addEventListener("click", function(e){ alert('Removing not yet supported!'); }, false); controlsDiv.appendChild(editButton); controlsDiv.appendChild(removeButton); frameDiv.appendChild(controlsDiv); // We now have a full question display DIV contained in the frameDiv variable. We should now add this to the sequencer content. } } function updateDividers() { var content = document.getElementById("seqContentWrapper"); for (var i = 0; i < content.childNodes.length; i++) { var element = content.childNodes[i]; if (!hasClass(element, "displayStep")) { continue; } var lastElement = (element.previousSibling && element.previousSibling.nodeName == "DIV") ? element.previousSibling : false; var nextElement = (element.nextSibling && element.nextSibling.nodeName == "DIV") ? element.nextSibling : false; if (lastElement != false) { if (!hasClass(lastElement, "divider")){ var newDivider = document.createElement("div"); addClass(newDivider, "divider"); addClass(newDivider, sequencer.settings.content.orientation); content.insertBefore(newDivider, element); delete newDivider; } } if (nextElement != false) { if (!hasClass(nextElement, "divider")){ var newDivider = document.createElement("div"); addClass(newDivider, "divider"); addClass(newDivider, sequencer.settings.content.orientation); content.insertBefore(newDivider, nextElement); delete newDivider; } } } } function savePipeline (confirmSave) { debugger; var answer; if (confirmSave == true) { answer = confirm("Save changes to pipeline?"); } else { answer = true; } if (answer == false) return; var requestString = "uids="; requestString += arrayToString(sequencer.session.pipeline.uids, ","); requestString = requestString.slice(0, requestString.length - 1); // remove trailing commas requestString += "&types="; requestString += arrayToString(sequencer.session.pipeline.types, ","); requestString = requestString.slice(0, requestString.length - 1); // remove trailing commas requestString += "&sessionUid="; requestString += sequencer.session.uid; console.log(requestString); newAjaxRequest(requestString, "savesession.php", function(result){ console.log(result.responseText); }, true); } function editStep() { // first save //savePipeline(false); var postForm = document.createElement("form"); var type = sequencer.session.pipeline.types[sequencer.session.pipeline.uids.indexOf(sequencer.state.selectedStep.uid)]; postForm.action = type.toLowerCase()+"Editor.php"; postForm.method = "POST"; var objectUid = document.createElement("input"); objectUid.type = "hidden"; objectUid.name = "objectUid"; objectUid.value = sequencer.state.selectedStep.uid; postForm.appendChild(objectUid); postForm.submit(); } function moveStep (direction) { // Check if a step is selected if (sequencer.state.selectedStep.uid == null || direction == null) return; // Check if the step is not at either end of the pipeline var index = sequencer.session.pipeline.uids.indexOf(sequencer.state.selectedStep.uid); if ((index < 0) || (index >= sequencer.session.pipeline.uids.length) || (index == 0 && direction == -1) || (index == sequencer.session.pipeline.uids.length - 1 && direction == 1)) { alert("Cannot move out of bounds!"); return; } // Find the two elements in the editor content display var content = document.getElementById("seqContentWrapper"); var element = document.getElementById(sequencer.session.pipeline.uids[index]); var otherElement = document.getElementById(sequencer.session.pipeline.uids[index+direction]); // First buffer the two elements var tempElement = element.cloneNode(true); var tempOtherElement = otherElement.cloneNode(true); var placeHolderElement = document.createElement("div"); placeHolderElement.id = "placeholder_element"; content.replaceChild(placeHolderElement, otherElement); content.replaceChild(tempOtherElement, element); content.replaceChild(tempElement, placeHolderElement); //This should work. // A-B Start positions, backup to tA and tB // A-X Replace B with placeholder X // B-X Replace A with tB // B-A Replace placeholder X with tA. // The two elements are now swapped. // Now swap the array entries. sequencer.session.pipeline.uids[index] = sequencer.session.pipeline.uids.splice(index+direction, 1, sequencer.session.pipeline.uids[index])[0]; sequencer.session.pipeline.types[index] = sequencer.session.pipeline.types.splice(index+direction, 1, sequencer.session.pipeline.types[index])[0]; } function initEditor() { // load settings fields first var fContentType = document.getElementById("sContentTypeField"); var content = document.getElementById("seqContent"); //sequencer.settings.content.contentType = fContentType.value.toLowerCase(); sequencer.settings.content.contentType = "survey"; var desiredWidth, desiredHeight; //Then select settings from a few presets switch (sequencer.settings.content.contentType) { case "session": sequencer.settings.content.orientation = "h"; sequencer.settings.content.width = 800; sequencer.settings.content.height = 125; content.style.width = sequencer.settings.content.width+"px"; content.style.height = sequencer.settings.content.height+"px"; break; case "survey": sequencer.settings.content.orientation = "v"; sequencer.settings.content.width = 600; sequencer.settings.content.height = "auto"; content.style.width = sequencer.settings.content.width+"px"; content.style.height = sequencer.settings.content.height+"px"; break; default: break; } }