Picture of sean callahan
Component feedback
by sean callahan - Tuesday, 22 August 2017, 4:59 PM
 

Hi,

I'm trying to figure out how to get the incorrect and partly correct feedback to display on a custom component. I have the correct feedback displaying properly. It seems I'm either not calling the other feedback options correctly or there is some other way to set up the feedback that I'm not seeing.

I see examples of components that have isCorrect for individual answers. I don't see any examples that relate to what I'm doing, which is checking the order of a sortable list and providing feedback based on if the list is in proper order or not.

I've looked in the quetionView.js file but it's not clear to me what it's looking for to get the other feedback options to display.

Heres the code I'm using:

JS file:

checkAnswers: function() {
   var numCorrect = 0;
   var numQs = this.getNumQuestions();


   $(".sortable-item-inner").each(function(index) {
      if ($.trim($(this).text()) == correctOrder[index]) numCorrect++;
   });

   if (numCorrect == numQs) {
      this.setupCorrectFeedback();
      console.log('correct')
   } else if (numCorrect > 0) {
      this.setupPartlyCorrectFeedback();
      console.log('partial correct')
   } else {
      this.setupIncorrectFeedback();
      console.log('incorrect')
   }
   this.showFeedback();
},

getNumQuestions: function() {
   return this.model.get("_items").length;
},

setUpCorrectFeedback: function() {
   this.model.set({
      feedbackTitle: this.model.get('title'),
      feedbackMessage: this.model.get('_feedback.correct')
   });
},

setupPartlyCorrectFeedback: function() {
   this.model.set({
      feedbackTitle: this.model.get('title'),
       feedbackMessage: this.model.get('_feedback._partlyCorrect.final') // Not working
   });
   console.log('fb msg partlyCorrect ' + this.model.get('_feedback._partlyCorrect.final'))
},

setupIncorrectFeedback: function() {
   this.model.set({
      feedbackTitle: this.model.get('title'),
      feedbackMessage: this.model.get('_feedback._incorrect.final') // Not working
   });
   console.log('fb msg incorrect ' + this.model.get('_feedback._incorrect.final'))
},

 

components file:

{ "_id": "c-45",
"_parentId": "b-45",
"_type":"component",
"_component":"sortable",
"_classes":"sortableComponent",
"_layout":"center",
"title":"Sortable Component",
"displayTitle":"Sortable Component",
"body":"Sortable Component",
"instruction":"Sortable Component",
"_isRandom": true,
"_selectable": 0,
"_canShowMarking" : false,
"_canShowFeedback": "true",
"_itemOrder" : "",
"_items": [
{
"text": "1917"
},
{
"text": "1888"
},
{
"text": "1953"
},
{
"text": "1977"
}
],
"_buttons": {
"_submit": "Submit",
"_reset": "Reset"
},
"_pageLevelProgress": {
"_isEnabled": true
},
"_feedback": {
"correct": "Correct answer feedback.<br><br>You put all the answers in the correct order.",
"_incorrect": {
"notFinal": "",
"final": "Incorrect answer feedback.<br><br>This feedback will appear if you answered the question incorrectly."
},
"_partlyCorrect": {
"notFinal": "",
"final": "Partly correct answer feedback.<br><br> This feedback will appear if you answered the question correctly."
}
}
},

 

Thanks,

Sean

Picture of Oliver Foster
Re: Component feedback
by Oliver Foster - Wednesday, 23 August 2017, 5:15 PM
 

Hi Sean,

 

These are the functions you need https://github.com/adaptlearning/adapt_framework/blob/master/src/core/js/views/questionView.js#L434-L490

which have a model counterparts https://github.com/adaptlearning/adapt_framework/blob/master/src/core/js/models/questionModel.js#L144-L229.

 

You basically need to setup your correctness functions isCorrect() and isPartlyCorrect() properly by the looks of it. Neither of which you've included in your javascript dump.

 

Your checkAnswers looks like a combination of markQuestion, isCorrect, setScore, isPartlyCorrect and setupFeedback. markQuestion and setupFeedback are both handled by Adapt, you just need to do isCorrect, setScore and isPartlyCorrect?

 

Kind Regards,

 

Ollie

Picture of sean callahan
Re: Component feedback
by sean callahan - Wednesday, 23 August 2017, 5:42 PM
 

If I'm understanding correctly I can use my own function to check the order, but then based on the results I should set isCorrect and isPartlyCorrect, and set the score. Then when I call show feedback it will automatically choose the proper feedback based on the isCorrect and isPartlyCorrect functions value. So if isCorrect and isPartlyCorrect are both set to false it will choose the incorrect feedback. That must mean the reason that only correct feedback is showing currently is that by default correct is set to true.

Picture of Oliver Foster
Re: Component feedback
by Oliver Foster - Thursday, 24 August 2017, 5:40 PM
 

The assumption you've made is incorrect. There is no default correctness attribute nor any default isCorrect behaviour.

Your component bypasses the usual behaviour of the questionView - which would normally choose the feedbackTitle and feedbackMessage for you. Instead your code is managing the selection of the feedbackTitle and feedbackMessage and so I would assume it's your code which is incorrectly choosing the feedback to show (as showFeedback takes what you give it).

 

You'll need to debug your code to see what's going on.

Picture of sean callahan
Re: Component feedback
by sean callahan - Thursday, 24 August 2017, 9:13 PM
 

First of all, thank you for correcting my incorrect assumptions. I appreciate that and I want to learn more about how adapt functions. That being said, it seems like it's sometimes difficult to get detailed answers to specific questions on this forum.

1) Is there any chance that someone from adapt is going to provide some up to date and clear documentation and examples at any point? It's not exactly easy to find out information about how to use adapt by looking at limited documentation in git repos. This is especially true for someone like me that is new to backbone.js. I think it would help to eliminate confusion if the docs were better put together and more well organized. As it is this forum is basically the only resource to get any answers.

2) I'm trying to follow the adapt-contrib-matching component to put this in order as it should be but it's confusing to me how for example the _isCorrect function is setting values like this:

this.model.set('_numberOfCorrectAnswers', numberOfCorrectAnswers);
this.model.set('_isAtLeastOneCorrectSelection', true);

Those fields are not in the components.json file, the questionView file, or in the example file, while fields like  _isCorrect are in the components file. I can see that  _isCorrect is being set per _item but where is _isAtLeastOneCorrectSelection coming from?

3) I've restructured my code (below) to work more like the matching component. I've tried setting values for isCorrect both per _item and just for a single field for the whole component. I'm not asking for anyone to fix my code but a clear example of how this works on a basic level would be really helpful.

 

buttonClick: function(event) {
   this.checkAnswers();
   this.showFeedback();
},

postRender: function() {
   this.sortable();
   this.setupQuestion();
   this.setupFeedback();

   this.setReadyStatus();

},

getCorrectAnswerOrder: function() {
   var itemOrder = this.model.get("_items");
   if (itemOrder && itemOrder.length > 0) {
      for (var i = 0, l = itemOrder.length; i < l; i++) {
         if (itemOrder[i].text) {
            correctOrder[i] = itemOrder[i].text;
         }
      }
   }
},

setupQuestion: function() {
   this.model.set('_itemOrder', []);
   this.setupQuestionItemIndexes();
   this.setScore();
   this.setupFeedback();
},

 

checkAnswers: function() {
   var numCorrect = 0;
   var numQs = this.model.get("_items").length;;
   $(".sortable-item-inner").each(function(index) {
      if ($.trim($(this).text()) == correctOrder[index]) numCorrect++;
   });
   return numCorrect;
},

 

isCorrect: function() {
   var numberOfCorrectAnswers = this.checkAnswers();

   this.model.set('_numberOfCorrectAnswers', numberOfCorrectAnswers);

   if (numberOfCorrectAnswers > 0) {
      this.model.set('_isAtLeastOneCorrectSelection', true);
   }

   if (numberOfCorrectAnswers === this.model.get('_items').length) {
      this.model.set('_isAtLeastOneCorrectSelection', true);
      this.model.set('_isCorrect', true);
      return true;
   } else {
      return false;
   }
},

setScore: function() {
   var questionWeight = this.model.get("_questionWeight");

   // Not getting _isCorrect here

   if (this.model.get('_isCorrect')) {
      this.model.set('_score', questionWeight);
      return;
   }

   var numberOfCorrectAnswers = this.checkAnswers();
   var itemLength = this.model.get('_items').length;

   var score = questionWeight * numberOfCorrectAnswers / itemLength;

   this.model.set('_score', score);
},

isPartlyCorrect: function() {
   return this.model.get('_isAtLeastOneCorrectSelection');
},

setupQuestionItemIndexes: function() {
   var items = this.model.get("_items");
   if (items && items.length > 0) {
      for (var i = 0, l = items.length; i < l; i++) {
         if (items[i]._index === undefined) items[i]._index = i;
      }
   }

},

setupRandomisation: function() {
   if (this.model.get('_isRandom') && this.model.get('_isEnabled')) {
      this.model.set("_items", _.shuffle(this.model.get("_items")));
   }
},

sortable: function() {

/* JQUERY UI SORTABLE FOR REARRANGING ANSWER ORDER */
   $(".sortable").sortable({
      revert: 100,
      containment: ".sortableWrap",
      forceHelperSize: true,
      cursor: "move",
      scroll: true,
      start: function(event, ui) {

      },
      change: function(event, ui) {

      },
      stop: function(event, ui) {

      }
   });

   $(".sortable").on( "sort", function( event, ui ) {
      $(".ui-sortable-helper").css({
         "box-shadow" : "-3px 3px 5px rgba(0,0,0,.5)",
         "margin" : "0",
         "left" : "0",
      });
   });

   $(".sortable").on("sortstop", function( event, ui ) {
   $(".sortable-item").attr("style", "");
   $(".sortable-item").css("margin", "6px 0 6px 12px");
});

 

Picture of Dan Storey
Re: Component feedback
by Dan Storey - Friday, 25 August 2017, 9:10 AM
 

Hi Sean,

It looks like you're doing pretty well, considering the documentation is not yet as detailed as you'd like.

1. Although I can't speak to when the docs will be updated it may also be worth noting that there is another place you can get support - the gitter rooms - which use a chat, rather than forum based format. Lots of friendly, super helpful people on there :)

 

https://gitter.im/adaptlearning/adapt_framework

2. You can set any property you want on the model using this.model.set("someProperty", "someValue"). This is a Backbone Model feature. I've found the Backbone docs really helpful at times

http://http://backbonejs.org/#Model-set

The reason the property is not in the component defaults is because it wouldn't necessarily be relevant to every component type e.g. textInput. It's set on the matching component so that it can be used to determine the value that .isPartlyCorrect returns. Like I say, you can store any properties you like to help determine isCorrect and isPartlyCorrect. I hope that helps.

3. With regard to your code, I'd remove this.model.set('_isCorrect', true) from your .isCorrect method. You should simply return either true or false and the questionModel will take care of this for you. You also won't need this.model.set('_isAtLeastOneCorrectSelection', true) the second time.

If you are not getting _isCorrect in your setScore function it's because your isCorrect method has returned false. Ollie's right - you'll need to do some debugging here, like logging numberOfCorrectAnswers to the console to see what's going on. You are also using a correctOrder array in there but I can't see where that's defined.

One more thing which might help at this stage while the docs are not yet there, is looking at questionModel.js

https://github.com/adaptlearning/adapt_framework/blob/master/src/core/js/models/questionModel.js

You'll see a number of noop methods e.g. restoreUserAnswers which are designed to be overridden in each component and make fit for your own purpose.

Good luck!

Just about to send and spotted this. Instead of 

$(".sortable-item-inner").each(function(index) {
      if ($.trim($(this).text()) == correctOrder[index]) numCorrect++;
   });

You may need

$(".sortable-item-inner").each(function(index, element) {
      if ($.trim($(element).text()) == correctOrder[index]) numCorrect++;
   });

As "this" refers to the $(".sortable-item-inner") collection rather than the iterated item. Does that work?

Cheers,

Dan

Picture of Dan Storey
Re: Component feedback
by Dan Storey - Friday, 25 August 2017, 10:32 AM
 

Hi Sean, me again.

My apologies, nothing wrong with use of "this" above so the error's coming from somewhere else.

One other thing though - when using jquery selectors you should bind them to the scope of the component like so (it's a Backbone View feature):

this.$(".sortable-item-inner")

Otherwise you'll run into problems when you have more than one of the same component on the page. Trust me, I speak from experience!

Picture of sean callahan
Re: Component feedback
by sean callahan - Friday, 25 August 2017, 4:37 PM
 

Hi Dan,

Got it that makes sense! Thanks for the info. I'll try out the gitter rooms too, great resource. I should be able to get this feedback working now. Really good to know about binding the jquery as I am trying to make all these components work with multiple instances on the page!

I've made some really cool display components, and once I get this piece of the puzzle down I'll have a good handle on question components too. I have some really awesome ones that I'd like to share with the community soon.  

Picture of Oliver Foster
Re: Component feedback
by Oliver Foster - Friday, 25 August 2017, 7:37 PM
 

3. A brief example:

QuetionView + ButtonsView work together:
https://github.com/adaptlearning/adapt_framework/blob/master/src/core/js/views/questionView.js#L112-L145

ButtonsView goes through these states to control the flow of questionView:
https://github.com/adaptlearning/adapt_framework/blob/master/src/core/js/enums/buttonStateEnum.js#L4-L10

The state is always at "SUBMIT" on first visit. When the submit button is clicked this function executes:
https://github.com/adaptlearning/adapt_framework/blob/master/src/core/js/views/questionView.js#L155-L211

(You can almost ignore the _runModelCompatibleFunction wrapper, this just says to either run the function from the model if available, or run the function on the view if no model is supplied - legacy style, we're trying to shift to have models for each component).

From the view function onSubmitClicked you should be able to see the order in which the model functions are executed. (Anything not wrapped in _runModelCompatibleFunction is a view only function, such as: showInstructionError, onCannotSubmit, removeInstructionError, showMarking, recordInteraction, showFeedback, onSubmitted).
The order of the model and view function executions when onSubmitClick is executed are:

  model.canSubmit (your code),

view.showInstructionError (core),

  view.onCannotSubmit (your code),

model.updateAttempts (core),

model.setQuestionAsSubmitted (core),

view.removeInstructionError (core),

  model.storeUserAnswer (your code), // specifically for restoring the state

model.markQuestion (core),

  model.isCorrect (your code),

  model.isPartlyCorrect (your code),

  model.setScore (your code),

  view.showMarking (your code),

model.checkQuestionCompletion (core),

  view.recordInteraction (your code), // specifically for scorm cmi.interactions

model.setupFeedback (core),

model.setupCorrectFeedback (core), 

model.setupPartlyCorrectFeedback (core), 

model.setupIncorrectCorrectFeedback (core),

model.updateButtons (code),

view.showFeedback (core),

  view.onSubmitted (your code) // mostly unneeded

Any of these functions can be overwritten on your model or view if you need bespoke behaviour, the ones with (core) next to them have default behaviour, the ones with (your code) need you to write something. There are only 6 functions that are really mandatory (if you exclude onSubmitted, storeUserAnswer and recordInteraction).

The attributes used and changed on the model with the core code include these:
_attemptsLeft,

_isEnabled,

_isSubmitted,

_isCorrect,

_isInteractionComplete,

_buttonState,

_canShowModelAnswer,

feedbackTitle,

feedbackMessage


Any other attributes are not part of this feedback process. They may be used but the core framework (see the inheritance chains of each core type, AdaptView>ComponentView>QuestionView, and AdaptModel>ComponentModel>QuestionModel) or by the component itself.

MCQ is a really good example of a question. 

Sorry that you're frustrated by the lack of docs. We're getting there slowly.

 

Ollie