app/exercises/exercise.js
/** Modular Zeeguu Powered Exercise @author Martin Avagyan
* @initialize it using: new Exercise();
* @customize it by using prototypal inheritance
**/
import $ from 'jquery';
import events from '../pubsub';
import Util from '../util';
import Settings from '../settings';
import Session from '../session';
import {Loader} from '../loader';
import ShakeAnimation from "../animations/shake_animation";
import Feedback from "../feedback";
var Exercise = function(data,index,size){
this.init(data,index,size);
//TODO unbind method
};
Exercise.prototype = {
/************************** SETTINGS ********************************/
data: 0,
customTemplateURL: 0,
index: 0,
startIndex: 0,
size: 0, //default number of bookmarks
description: "Solve the exercise", //default description
session: Session.getSession(), //Example of session id 34563456 or 11010001
startTime: 0,
isHintUsed: false,
minRequirement: 1,
resultSubmitSource: Settings.ZEEGUU_EX_SOURCE_RECOGNIZE,//Defualt submission
successAnimationTime: 2000,
exFeedback: 0,
/*********************** General Functions ***************************/
/**
* Loads the HTML exercise template from static
**/
createCustomDom: function(){
Loader.loadTemplateIntoElem(this.customTemplateURL,$("#custom-content"));
},
/**
* Saves the common dom in chache
**/
cacheDom: function(){
this.$elem = $("#ex-module");
this.$description = this.$elem.find("#ex-descript");
this.$loader = this.$elem.find('#loader');
this.$statusContainer = this.$elem.find('#ex-status-container');
this.$exFooterPrimary = this.$elem.find('#ex-footer-primary');
this.$exFooterSecondary = this.$elem.find('#ex-footer-secondary');
this.cacheCustomDom();
},
/**
* Exercise initialaizer
**/
init: function(data,index,size){
var _this = this;
$.when(Loader.loadTemplateIntoElem(_this.customTemplateURL,$("#custom-content"))).done(function(){
_this.cacheDom();
_this.bindUIActions();
_this._constructor(data,index,size);
});
},
/**
* The main constructor
**/
_constructor: function (data,index,size){
this.data = data;
this.index = index;
this.startIndex = index;
this.size = size;
this.shake = new ShakeAnimation();
this.exFeedback = new Feedback(this.resultSubmitSource,this.session);
this.setDescription();
this.next();
this.startTime = Date.now();
},
/**
* Populates custom exercise description
**/
setDescription: function(){
this.$description.html(this.description);
},
/**
* When the ex are done, notify the observers
**/
onExComplete: function (){
events.emit('exerciseCompleted');
},
/**
* Check selected answer with success condition
**/
checkAnswer: function (chosenWord){
if (this.successCondition(chosenWord)){
this.onSuccess();
return;
}
this.wrongAnswerAnimation();
this.submitResult(this.data[this.index].id,Settings.ZEEGUU_EX_OUTCOME_WRONG);
},
/**
* Actions taken when the succes condition is true
**/
onSuccess: function(){
this.$exFooterPrimary.removeClass ('mask-appear');
this.$exFooterSecondary.toggleClass('mask-appear');
this.handleSuccessCondition();
},
/**
* Revert the secondary and primary footers
* */
revertPrimary: function () {
this.$exFooterSecondary.removeClass ('mask-appear');
this.$exFooterPrimary.toggleClass('mask-appear');
},
/**
* When the answer is successful show the animation and submit the result
* */
handleSuccessCondition: function () {
this.animateSuccess();
//Submit the result of translation
this.submitResult(this.data[this.index].id,Settings.ZEEGUU_EX_OUTCOME_CORRECT);
// Notify the observer
events.emit('progress');
this.index++;
},
/**
* On success condition true, generate new exercise
* */
onRenderNextEx: function () {
this.revertPrimary();
// The current exercise set is complete
if(this.index == this.size + this.startIndex){
this.onExComplete();
return;
}
this.next();
this.startTime = Date.now();
},
/**
* Request the submit to the Zeeguu API
* e.g. https://www.zeeguu.unibe.ch/api/report_exercise_outcome/Correct/Recognize/1000/4726?session=34563456
**/
submitResult: function(id,exOutcome){
let _this = this;
//If the user used the hint, do not register correct solution, resent the hint, move on
if(this.isHintUsed && exOutcome == Settings.ZEEGUU_EX_OUTCOME_CORRECT) {
this.isHintUsed = false;
return;
}
//If hint is used twice, ignore request
if(this.isHintUsed && exOutcome == Settings.ZEEGUU_EX_OUTCOME_HINT) return;
//Calculate time taken for single exercise
var exTime = Util.calcTimeInMilliseconds(this.startTime);
//Request back to the server with the outcome
//console.log(Settings.ZEEGUU_API + Settings.ZEEGUU_EX_OUTCOME_ENDPOINT + exOutcome + _this.resultSubmitSource + "/" + exTime + "/" + id + "?session="+this.session);
$.post(Settings.ZEEGUU_API + Settings.ZEEGUU_EX_OUTCOME_ENDPOINT + exOutcome + _this.resultSubmitSource + "/" + exTime + "/" + id + "?session="+this.session);
},
/**
* Removes focus of page elements
**/
prepareDocument: function(){
if (document.activeElement != document.body) document.activeElement.blur();
},
/**
* User hint handler
**/
handleHint: function(){
this.submitResult(this.data[this.index].id,Settings.ZEEGUU_EX_OUTCOME_HINT);
this.isHintUsed = true;
this.giveHint();
},
/**
* Function for sending the user feedback for an individual exercise
* */
giveFeedbackBox: function () {
this.exFeedback.exerciseFeedbackBox(this.data[this.index-1].id);
},
/**
* Apply style dynamically when content changes
* */
reStyleDom: function () {
this.reStyleContext();
},
/**
* Apply style dynamically to context
* */
reStyleContext: function () {
if(!Util.isMultiline(this.$context)){
this.$context.addClass('centering');//Text align center
return;
}
this.$context.removeClass('centering');//Text align justify
},
/*********************** Interface functions *****************************/
/**
* Binding UI with Controller functions
**/
bindUIActions: function(){},
/**
* Condition used by checkAnswer
* If true, then a correct answer was given
**/
successCondition: function(){},
/**
* Gives a hint when the hint button is pressed
**/
giveHint: function (){},
/**
* Populates the next exercise
**/
next: function (){},
/**
* Cahes custom dom of the exercise
**/
cacheCustomDom: function(){},
/**
* Animation for wrong solution
**/
wrongAnswerAnimation: function(){},
/************************** Animations ********************************/
/**
* Animation for successful solution
**/
animateSuccess: function(){
let _this = this;
this.$statusContainer.removeClass('hide');
setTimeout(function(){
if (_this.$statusContainer.length > 0) {
_this.$statusContainer.addClass('hide');
}
}, _this.successAnimationTime);
},
};
export default Exercise;