Skip to content
Open
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "adapt-contrib-scoring",
"version": "1.1.0",
"framework": ">=5.31.31",
"framework": ">=5.48.2",
"homepage": "https://github.com/adaptlearning/adapt-contrib-scoring",
"issues": "https://github.com/adaptlearning/adapt-contrib-scoring/issues/new",
"extension": "scoring",
Expand Down
141 changes: 113 additions & 28 deletions js/ScoringSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ export default class ScoringSet extends Backbone.Controller {
this._title = title;
this._isScoreIncluded = _isScoreIncluded;
this._isCompletionRequired = _isCompletionRequired;
// only register root sets as subsets are dynamically created when required
if (!this.subsetParent) this.register();
this.register();
this._setupListeners();
}

Expand All @@ -56,6 +55,7 @@ export default class ScoringSet extends Backbone.Controller {
* @fires Adapt#scoring:set:register
*/
register() {
if (this.subsetParent) return;
Adapt.scoring.register(this);
Adapt.trigger(`scoring:${this.type}:register scoring:set:register`, this);
}
Expand All @@ -64,6 +64,8 @@ export default class ScoringSet extends Backbone.Controller {
* @protected
*/
_setupListeners() {
if (this.subsetParent || this.type === 'adapt') return;
this.listenTo(Adapt, 'questionView:submitted', this.onQuestionSubmitted);
if (OfflineStorage.ready) return this.restore();
this.listenTo(Adapt, 'offlineStorage:ready', this.restore);
}
Expand All @@ -75,10 +77,14 @@ export default class ScoringSet extends Backbone.Controller {
* @fires Adapt#scoring:set:restored
*/
restore() {
if (this.subsetParent) return;
Adapt.trigger(`scoring:${this.type}:restored scoring:set:restored`, this);
}

init() {
this._setObjectiveStatus = _.debounce(this._setObjectiveStatus, 100);
this._wasAvailable = this.isAvailable;
this._wasIncomplete = this.isIncomplete;
this._wasComplete = this.isComplete;
this._wasPassed = this.isPassed;
this._initializeObjective();
Expand All @@ -89,9 +95,12 @@ export default class ScoringSet extends Backbone.Controller {
*/
update() {
const isComplete = this.isComplete;
if (isComplete && !this._wasComplete) this.onCompleted();
const isPassed = this.isPassed;
if (isPassed && !this._wasPassed) this.onPassed();
if (isComplete && !this._wasComplete && this._wasAvailable) this.onCompleted();
if (isPassed && !this._wasPassed && this._wasAvailable) this.onPassed();
if (this.hasStatusChanged) this._setObjectiveStatus();
this._wasAvailable = this.isAvailable;
this._wasIncomplete = this.isIncomplete;
this._wasComplete = isComplete;
this._wasPassed = isPassed;
}
Expand All @@ -102,6 +111,7 @@ export default class ScoringSet extends Backbone.Controller {
* @fires Adapt#scoring:set:reset
*/
reset() {
if (this.subsetParent) return;
Adapt.trigger(`scoring:${this.type}:reset scoring:set:reset`, this);
Logging.debug(`${this.id} reset`);
this._resetObjective();
Expand Down Expand Up @@ -357,23 +367,48 @@ export default class ScoringSet extends Backbone.Controller {
* @returns {boolean}
*/
get canReset() {
return false
return false;
}

/**
* Returns whether the set is optional
* @returns {boolean}
*/
get isOptional() {
return false
return false;
}

/**
* Returns whether the set is available
* @returns {boolean}
*/
get isAvailable() {
return true
return true;
}

/**
* Returns whether the set is started
* @returns {boolean}
*/
get isStarted() {
return this.models.some(model => model.get('_isVisited'));
}

/**
* Returns whether the set is started and incomplete
* @returns {boolean}
*/
get isIncomplete() {
return this.isStarted && !this.isComplete;
}

/**
* Returns whether the objective for the set is completed.
* Depending on the set logic, this can differ to `_isComplete`.
* @returns {boolean}
*/
get isObjectiveComplete() {
return this.isComplete;
}

/**
Expand All @@ -384,8 +419,13 @@ export default class ScoringSet extends Backbone.Controller {
Logging.error(`isComplete must be overriden for ${this.constructor.name}`);
}

get isIncomplete() {
return (this.isComplete === false);
/**
* Returns whether the objective for the set is passed.
* Depending on the set logic, this can differ to `isPassed`.
* @returns {boolean}
*/
get isObjectivePassed() {
return this.isPassed;
}

/**
Expand All @@ -400,51 +440,85 @@ export default class ScoringSet extends Backbone.Controller {
return (this.isPassed === false);
}

/**
* Check whether the status has changed since the last `update`
* @returns {boolean}
*/
get hasStatusChanged() {
return this.isAvailable !== this._wasAvailable ||
this.isIncomplete !== this._wasIncomplete ||
this.isComplete !== this._wasComplete ||
this.isPassed !== this._wasPassed;
}

/**
* Define the objective for reporting purposes
* @protected
*/
_initializeObjective() {
if (this.subsetParent) return;
const id = this.id;
const description = this.title;
const completionStatus = COMPLETION_STATE.NOTATTEMPTED.asLowerCase;
OfflineStorage.set('objectiveDescription', id, description);
if (this.isComplete) return;
OfflineStorage.set('objectiveStatus', id, completionStatus);
if (this.subsetParent || this.isStarted) return;
OfflineStorage.set('objectiveDescription', this.id, this.title);
this._setObjectiveStatus();
}

/**
* Reset the objective data
* @protected
*/
_resetObjective() {
if (this.subsetParent || this.isComplete) return;
const id = this.id;
const completionStatus = COMPLETION_STATE.INCOMPLETE.asLowerCase;
OfflineStorage.set('objectiveScore', id, this.score, this.minScore, this.maxScore);
OfflineStorage.set('objectiveStatus', id, completionStatus);
if (this.subsetParent || this.isObjectiveComplete || !this.hasStatusChanged) return;
this._setObjectiveScore();
this._setObjectiveStatus();
}

/**
* Complete the objective
* @todo Always updates to latest data - is this desired?
* Complete the objective.
* Will update to the latest data/attempt unless overriden in a subset.
* @protected
*/
_completeObjective() {
if (this.subsetParent) return;
const id = this.id;
const completionStatus = COMPLETION_STATE.COMPLETED.asLowerCase;
const successStatus = (this.isPassed ? COMPLETION_STATE.PASSED : COMPLETION_STATE.FAILED).asLowerCase;
OfflineStorage.set('objectiveScore', id, this.score, this.minScore, this.maxScore);
OfflineStorage.set('objectiveStatus', id, completionStatus, successStatus);
this._setObjectiveScore();
this._setObjectiveStatus();
}

/**
* Set the objective score
* @protected
*/
_setObjectiveScore() {
if (this.subsetParent) return;
OfflineStorage.set('objectiveScore', this.id, this.score, this.minScore, this.maxScore);
}

/**
* Set the appropriate objective completion and success status.
* Will update to the latest data/attempt, unless controlled accordingly in a subset.
* @protected
*/
_setObjectiveStatus() {
if (this.subsetParent) return;
const isAvailable = this.isAvailable;
const isIncomplete = this.isIncomplete;
const isComplete = this.isObjectiveComplete;
const isPassed = this.isObjectivePassed;
let completionStatus = COMPLETION_STATE.UNKNOWN.asLowerCase;
let successStatus = COMPLETION_STATE.UNKNOWN.asLowerCase;
if (isAvailable && !isIncomplete) completionStatus = COMPLETION_STATE.NOTATTEMPTED.asLowerCase;
if (isAvailable && isIncomplete) completionStatus = COMPLETION_STATE.INCOMPLETE.asLowerCase;
if (isAvailable && isComplete) {
completionStatus = COMPLETION_STATE.COMPLETED.asLowerCase;
if (this.passmark.isEnabled) successStatus = (isPassed ? COMPLETION_STATE.PASSED : COMPLETION_STATE.FAILED).asLowerCase;
}
OfflineStorage.set('objectiveStatus', this.id, completionStatus, successStatus);
}

/**
* @fires Adapt#scoring:[set.type]:complete
* @fires Adapt#scoring:set:complete
*/
onCompleted() {
if (this.subsetParent) return;
Adapt.trigger(`scoring:${this.type}:complete scoring:set:complete`, this);
Logging.debug(`${this.id} completed`);
this._completeObjective();
Expand All @@ -455,8 +529,19 @@ export default class ScoringSet extends Backbone.Controller {
* @fires Adapt#scoring:set:passed
*/
onPassed() {
if (this.subsetParent) return;
Adapt.trigger(`scoring:${this.type}:passed scoring:set:passed`, this);
Logging.debug(`${this.id} passed`);
}

/**
* @param {QuestionView} view
* @listens Adapt#questionView:submitted
*/
onQuestionSubmitted(view) {
const model = view.model;
if (!this.questions.includes(model)) return;
model.addContextActivity(this.id, this.type, this.title);
}

}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "adapt-contrib-scoring",
"version": "1.1.0",
"framework": ">=5.31.31",
"framework": ">=5.48.2",
"homepage": "https://github.com/adaptlearning/adapt-contrib-scoring",
"issues": "https://github.com/adaptlearning/adapt-contrib-scoring/issues/new",
"extension": "scoring",
Expand Down