Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Little refactorings for tutorial.js #139

Merged
merged 7 commits into from
Jul 14, 2020
4 changes: 3 additions & 1 deletion _layouts/tutorials.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ <h3>MEI Editor
<button id="btn-toggleHint" class="btn btn-link float-right disabled" title="trick or cheat">show hint</button>
</h3>
<div id="editorBox">
<div id="editor"></div>
<div id="editor">
<!-- editor content goes in here -->
</div>
</div>

<div id="hints" style="display: none;">
Expand Down
240 changes: 122 additions & 118 deletions js/mei-tutorials.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,24 @@ function setupTutorial(data) {
a.innerHTML = text;
li.appendChild(a);
stepBox.appendChild(li);


//initial step
var stepNum = 0 ;

//start with the first step of the tutorial
loadTutorialStep(data,0);
loadTutorialStep(data, stepNum);

//add listener to allow going to the next step
document.getElementById('nextStepButton').addEventListener('click',(e) => {
var stepNum = parseInt(e.target.getAttribute('data-stepnum'));
stepNum = parseInt(e.target.getAttribute('data-next-stepnum'), 10);

//catch any non numbers
if (isNaN(stepNum)) {
console.log('error getting next step number: ', stepNum, 'for event: ', e);
}

// load next step
loadTutorialStep(data,stepNum + 1);
//load next step
loadTutorialStep(data, stepNum);
});
}

Expand All @@ -133,37 +141,37 @@ function loadTutorialStep(data, stepNum) {

console.log('\nloading step ' + stepNum + ', maximum step is ' + data.steps.length);

// do not allow download or continuation before tutorial is set up
disallowDownload();
blockNextStep();

//if all steps are passed, move on to the final page, and skip rest of function
if(stepNum >= data.steps.length) {
showFinalPage(data);
return true;
}

// do not allow download or continuation before tutorial is set up
disallowDownload();
blockNextStep();

//retrieve step object from data
var step = data.steps[stepNum];
currentStep = step;

//adds heading as provided, falls back to plain step numbers
document.getElementById('stepLabel').innerHTML = (typeof step.label !== 'undefined' && step.label !== '') ? step.label : 'Step ' + (stepNum + 1);
document.getElementById('fullFileTitle').innerHTML = step.xmlFile;
document.getElementById('stepLabel').innerHTML = (typeof currentStep.label !== 'undefined' && currentStep.label !== '') ? currentStep.label : 'Step ' + (stepNum + 1);
document.getElementById('fullFileTitle').innerHTML = currentStep.xmlFile;

//activate current item
activateStepListItem(data,stepNum);
activateStepListItem(data, stepNum);

//decide if XML editor is needed on this page, and if it needs to be prefilled
var requiresEditor = (typeof step.xmlFile !== 'undefined' && step.xmlFile !== '') && typeof step.xpaths !== 'undefined' && step.xpaths.length > 0;
var requiresPrefill = (typeof step.prefillFile !== 'undefined' && step.prefillFile !== '');
var requiresEditor = (typeof currentStep.xmlFile !== 'undefined' && currentStep.xmlFile !== '') && typeof currentStep.xpaths !== 'undefined' && currentStep.xpaths.length > 0;
var requiresPrefill = (typeof currentStep.prefillFile !== 'undefined' && currentStep.prefillFile !== '');

//get relevant elements on page
var editorContainer = document.getElementById('editorContainer');
var nextStepButton = document.getElementById('nextStepButton');

//update nextStepButton to allow proceeding. listener is set in setupTutorial()
nextStepButton.setAttribute('data-stepnum',stepNum);
nextStepButton.setAttribute('data-next-stepnum',stepNum + 1);

//show editor only when needed, allow to proceed without interaction
if(!requiresEditor) {
Expand All @@ -173,9 +181,9 @@ function loadTutorialStep(data, stepNum) {
editorContainer.style.display = 'block';

//fetch XML file
var xmlPromise = fetchFile(step.xmlFile);
var xmlPromise = fetchFile(currentStep.xmlFile);
// fetch prefill file (if existing, otherwise return promise of empty string)
var prefillPromise = (requiresPrefill) ? fetchFile(step.prefillFile) : new Promise(function(resolve) { resolve(''); });
var prefillPromise = (requiresPrefill) ? fetchFile(currentStep.prefillFile) : new Promise(function(resolve) { resolve(''); });
// array of the promises to be resolved
var promiseArray = [xmlPromise, prefillPromise];

Expand All @@ -199,13 +207,13 @@ function loadTutorialStep(data, stepNum) {
}

//gets the description of the current step
fetchFile(step.descFile)
fetchFile(currentStep.descFile)
.then(function(descriptionFile) {
// update instruction section
document.getElementById('instruction').innerHTML = descriptionFile;
})
.catch(function(error) {
console.log('There has been a problem with the fetch operation for ', step.descFile, error.message);
console.log('There has been a problem with the fetch operation for ', currentStep.descFile, error.message);
});

}
Expand Down Expand Up @@ -237,124 +245,120 @@ function setupEditor(data, stepNum, xmlString, requiresPrefill, prefillString) {
// adjust size of editor box
resizeEditor(step.editorLines);

// check for editor changes by user input
handleEditorChanges(data, stepNum, step, file);
// watch out for editor changes by user input
editor.session.on('change', function changeListener(delta) {
// delta.start, delta.end, delta.lines, delta.action

handleEditorChanges(file);
});
}

/*
* function handleEditorChanges
*/
function handleEditorChanges(data, stepNum, step, file) {
function handleEditorChanges(file) {

var parser = new DOMParser();
var xmlDoc;

var isValid = false;
var wellformed = false;
var renderAnyway = false;

var editValue = '';
var validationString = '';

// watch out for changes by user input
editor.session.on('change', function changeListener(delta) {
// delta.start, delta.end, delta.lines, delta.action

// clean up hints and rendering
cleanUpHelpers();

// get user input
editValue = editor.getSession().getValue();

// update validation string
validationString = file.start + editValue + file.end;

// try to parse validation string into xmlDoc
try {
xmlDoc = parser.parseFromString(validationString, "text/xml");

// check if parsed xmlDoc is wellformed
wellformed = (xmlDoc.activeElement && xmlDoc.activeElement.localName && xmlDoc.activeElement.localName === 'parsererror') ? false : true;
} catch (error) {
console.log('parserError: ' + error);
}

if (!wellformed) {
console.log('not well-formed');
displayWarning('Your code is not well-formed.');
document.getElementById('rendering').innerHTML = '';

// do not allow download or continuation until file is wellformed
disallowDownload();
blockNextStep();
} else {
isValid = true;
var renderAnyway = true;

for (var i = 0; i < currentStep.xpaths.length; i++) {

var xpathResult;

try {
xpathResult = xmlDoc.evaluate(currentStep.xpaths[i].rule, xmlDoc, nsResolver, XPathResult.BOOLEAN_TYPE, null);
} catch (error) {
console.log('error resolving xpath: "' + currentStep.xpaths[i].rule + '"' + error);
isValid = false;
break;


// clean up hints and rendering
cleanUpHelpers();

// do not allow download or continuation until file is wellformed and valid
disallowDownload();
blockNextStep();

// get user input
editValue = editor.getSession().getValue();

// update validation string
validationString = file.start + editValue + file.end;

// try to parse validation string into xmlDoc
try {
xmlDoc = parser.parseFromString(validationString, "text/xml");

// check if parsed xmlDoc is wellformed
wellformed = (xmlDoc.activeElement && xmlDoc.activeElement.localName && xmlDoc.activeElement.localName === 'parsererror') ? false : true;
} catch (error) {
console.log('parserError: ' + error);
}

if (!wellformed) {
console.log('not well-formed');
displayWarning('Your code is not well-formed.');
document.getElementById('rendering').innerHTML = '';


} else {
isValid = true;
renderAnyway = true;

for (var i = 0; i < currentStep.xpaths.length; i++) {

var xpathResult;

try {
xpathResult = xmlDoc.evaluate(currentStep.xpaths[i].rule, xmlDoc, nsResolver, XPathResult.BOOLEAN_TYPE, null);
} catch (error) {
console.log('error resolving xpath: "' + currentStep.xpaths[i].rule + '"' + error);
isValid = false;
break;
}

if (!xpathResult.booleanValue) {

isValid = false;

if (!currentStep.xpaths[i].renderanyway) {
renderAnyway = false;
}

if (!xpathResult.booleanValue) {

isValid = false;

if (!currentStep.xpaths[i].renderanyway) {
renderAnyway = false;
}

// if there is no warning, let the user play without interruptions
if (typeof currentStep.xpaths[i].hint !== 'undefined' && currentStep.xpaths[i].hint !== '') {
var text = currentStep.xpaths[i].hint;
displayWarning(text);
text = '';
}
break;

// if there is no warning, let the user play without interruptions
if (typeof currentStep.xpaths[i].hint !== 'undefined' && currentStep.xpaths[i].hint !== '') {
var text = currentStep.xpaths[i].hint;
displayWarning(text);
text = '';
}
break;
}

// stop change propagation to prevent infinite loop
editor.session.off('change', changeListener);

// render if things are valid or renderable
if(isValid || renderAnyway) {
renderVerovio(validationString);

//if it's renderable, allow to download
allowDownload();

//copy current state into full-file editor
fullFileEditor.setValue(validationString);
fullFileEditor.clearSelection();

//make full file available for download
var downloadStr = 'data:text/xml;charset=utf-8,' + encodeURIComponent(validationString);
var downloadBtn = document.getElementById('fullFileDownloadBtn');
downloadBtn.setAttribute('href', downloadStr);
downloadBtn.setAttribute('download', currentStep.xmlFile);
}

//decide if user may continue or not
if (!isValid) {
blockNextStep();
disallowDownload();
} else {
allowNextStep();
}

// continue to listen for changes
handleEditorChanges(data, stepNum, currentStep, file);
}
});
}

// render if things are valid or renderable
if (isValid || renderAnyway) {
renderVerovio(validationString);

//copy current state into full-file editor
fullFileEditor.session.setValue(validationString);
fullFileEditor.clearSelection();

//make full file available for download
var downloadStr = 'data:text/xml;charset=utf-8,' + encodeURIComponent(validationString);
var downloadBtn = document.getElementById('fullFileDownloadBtn');
downloadBtn.setAttribute('href', downloadStr);
downloadBtn.setAttribute('download', currentStep.xmlFile);

//if it's renderable & everything is set up, allow to download
allowDownload();
}

//decide if user may continue or not
if (isValid) {
allowNextStep();
}

}


/*
* function showFinalPage
* @param data: Content of the tutorial's JSON file
Expand Down Expand Up @@ -426,7 +430,7 @@ function renderVerovio(validationString) {

if (error) {
// display message
document.getElementById('rendering').innerHTML = 'please adjust encoding to enable rendering';
document.getElementById('rendering').innerHTML = 'The current encoding cannot be rendered.';
} else {
// display svg
document.getElementById('rendering').innerHTML = svg;
Expand Down