Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 114 additions & 37 deletions problem_builder/public/js/mentoring_with_steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,120 @@ function MentoringWithStepsBlock(runtime, element) {
}

var children = runtime.children(element);

var steps = [];

for (var i = 0; i < children.length; i++) {
var child = children[i];
var blockType = $(child.element).data('block-type');
if (blockType === 'sb-step') {
steps.push(child);
registerStep(child);
}
}

var activeStep = $('.mentoring', element).data('active-step');
var activeStepIndex = $('.mentoring', element).data('active-step');
var attemptsTemplate = _.template($('#xblock-attempts-template').html());
var message = $('.sb-step-message', element);
var checkmark, submitDOM, nextDOM, reviewButtonDOM, tryAgainDOM,
gradeDOM, attemptsDOM, reviewLinkDOM, submitXHR;
var reviewStepDOM = $("div.xblock[data-block-type=sb-review-step], div.xblock-v1[data-block-type=sb-review-step]", element);
var hasAReviewStep = reviewStepDOM.length == 1;

/**
* Registers step with this StepBuilder instance: Creates and stores a wrapper that contains the XBlock instance
* and associated DOM element, as well as some additional metadata used to safely
* remove and re-attach xblock to the DOM. See showStep and hideStep to see why we need to do it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbzdak "Registers step with this StepBuilder instance: Creates and stores a wrapper that contains the XBlock instance and associated DOM element, as well as some additional metadata ..."

Very happy to see documentation for all of the new functions! :)

* @param step xblock instance to register
*/
function registerStep(step) {
var $element = $(step.element);
var $anchor = $('<span class="xblock-sb-anchor"/>');
$anchor.insertBefore($element);
var step_wrapper = {
$element: $element,
xblock: step,
$anchor: $anchor
};
steps.push(step_wrapper);
}

/**
* Returns wrapper for the active step
* @returns {*}, an object containing the XBlock instance and associated DOM element for the active step, as well
* as additional metadata (see registerStep where this object is created for properties)
*/
function getWrapperForActiveStep() {
return steps[activeStepIndex];
}

/**
* Returns the active step
* @returns {*}
*/
function getActiveStep() {
return getWrapperForActiveStep().xblock;
}

/**
* Calls a function for each registered step. The object passed to this function is a step wrapper object
* (see registerStep where this object is created for a list of properties)
*
* @param func single arg function.
*/
function forEachStep(func){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbzdak I like this function, it enables some nice refactorings :)

for (var idx=0; idx < steps.length; idx++) {
func(steps[idx]);
}
}

/**
* Displays a step
* @param step_wrapper
*/
function showStep(step_wrapper) {
step_wrapper.$element.show();
step_wrapper.xblock.updateChildren();
}

/**
* Hides a step
* @param step_wrapper
*/
function hideStep(step_wrapper) {
// This is a hacky workaround, but it works. It detaches this element from DOM, and re-attaches it immediately,
// which has the side effect of stopping any video elements (tested with Ooyala and Youtube).
// This solution was chosen as permanently detaching xblocks from DOM is not anticipated by:
// * Selenium tests
// * Some javascript code here
// * Anything else that inspects DOM elements and makes choices
step_wrapper.$element.detach();
step_wrapper.$element.insertAfter(step_wrapper.$anchor);
step_wrapper.$element.hide();
}

/**
* Displays the active step
*/
function showActiveStep() {
var step = getWrapperForActiveStep();
showStep(step);
}

/**
* Hides all steps
*/
function hideAllSteps() {
forEachStep(function(step){
hideStep(step);
});
}

function isLastStep() {
return (activeStep === steps.length-1);
return (activeStepIndex === steps.length-1);
}

function atReviewStep() {
return (activeStep === -1);
return (activeStepIndex === -1);
}

function someAttemptsLeft() {
Expand All @@ -49,7 +139,7 @@ function MentoringWithStepsBlock(runtime, element) {
} else {
checkmark.addClass('checkmark-incorrect icon-exclamation fa-exclamation');
}
var step = steps[activeStep];
var step = getActiveStep();
if (typeof step.showFeedback == 'function') {
step.showFeedback(response);
}
Expand Down Expand Up @@ -78,14 +168,14 @@ function MentoringWithStepsBlock(runtime, element) {
function submit() {
submitDOM.attr('disabled', 'disabled'); // Disable the button until the results load.
var submitUrl = runtime.handlerUrl(element, 'submit');

var hasQuestion = steps[activeStep].hasQuestion();
var data = steps[activeStep].getSubmitData();
data["active_step"] = activeStep;
var activeStep = getActiveStep();
var hasQuestion = activeStep.hasQuestion();
var data = activeStep.getSubmitData();
data["active_step"] = activeStepIndex;
$.post(submitUrl, JSON.stringify(data)).success(function(response) {
showFeedback(response);
activeStep = response.active_step;
if (activeStep === -1) {
activeStepIndex = response.active_step;
if (activeStepIndex === -1) {
// We are now showing the review step / end
// Update the number of attempts.
attemptsDOM.data('num_attempts', response.num_attempts);
Expand All @@ -102,27 +192,21 @@ function MentoringWithStepsBlock(runtime, element) {
}

function getResults() {
var step = steps[activeStep];
step.getResults(handleReviewResults);
getActiveStep().getResults(handleReviewResults);
}

function handleReviewResults(response) {
// Show step-level feedback
showFeedback(response);
// Forward to active step to show answer level feedback
var step = steps[activeStep];
var step = getActiveStep();
var results = response.results;
var options = {
checkmark: checkmark
};
step.handleReview(results, options);
}

function hideAllSteps() {
for (var i=0; i < steps.length; i++) {
$(steps[i].element).hide();
}
}

function clearSelections() {
$('input[type=radio], input[type=checkbox]', element).prop('checked', false);
Expand All @@ -139,7 +223,7 @@ function MentoringWithStepsBlock(runtime, element) {
}

function updateNextLabel() {
var step = steps[activeStep];
var step = getActiveStep();
nextDOM.attr('value', step.getStepLabel());
}

Expand All @@ -164,7 +248,7 @@ function MentoringWithStepsBlock(runtime, element) {
nextDOM.on('click', updateDisplay);
reviewButtonDOM.on('click', showGrade);

var step = steps[activeStep];
var step = getActiveStep();
if (step.hasQuestion()) { // Step includes one or more questions
nextDOM.attr('disabled', 'disabled');
submitDOM.show();
Expand Down Expand Up @@ -231,7 +315,7 @@ function MentoringWithStepsBlock(runtime, element) {
}

function jumpToReview(stepIndex) {
activeStep = stepIndex;
activeStepIndex = stepIndex;
cleanAll();
showActiveStep();
updateNextLabel();
Expand All @@ -245,7 +329,7 @@ function MentoringWithStepsBlock(runtime, element) {
nextDOM.show();
nextDOM.removeAttr('disabled');
}
var step = steps[activeStep];
var step = getActiveStep();

tryAgainDOM.hide();
if (step.hasQuestion()) {
Expand All @@ -269,11 +353,6 @@ function MentoringWithStepsBlock(runtime, element) {
} // Don't show attempts if unlimited attempts available (max_attempts === 0)
}

function showActiveStep() {
var step = steps[activeStep];
$(step.element).show();
step.updateChildren();
}

function onChange() {
// We do not allow users to modify answers belonging to a step after submitting them:
Expand All @@ -286,7 +365,7 @@ function MentoringWithStepsBlock(runtime, element) {

function validateXBlock() {
var isValid = true;
var step = steps[activeStep];
var step = getActiveStep();
if (step) {
isValid = step.validate();
}
Expand All @@ -298,15 +377,13 @@ function MentoringWithStepsBlock(runtime, element) {
}

function initSteps(options) {
for (var i=0; i < steps.length; i++) {
var step = steps[i];
var mentoring = {
forEachStep(function (step) {
options.mentoring = {
setContent: setContent,
publish_event: publishEvent
};
options.mentoring = mentoring;
step.initChildren(options);
}
step.xblock.initChildren(options);
});
}

function setContent(dom, content) {
Expand Down Expand Up @@ -346,7 +423,7 @@ function MentoringWithStepsBlock(runtime, element) {
}

function reviewNextStep() {
jumpToReview(activeStep+1);
jumpToReview(activeStepIndex+1);
}

function handleTryAgain(result) {
Expand All @@ -355,7 +432,7 @@ function MentoringWithStepsBlock(runtime, element) {
// and interrupting their experience with the current unit
notify('navigation', {state: 'lock'});

activeStep = result.active_step;
activeStepIndex = result.active_step;
clearSelections();
updateDisplay();
tryAgainDOM.hide();
Expand Down