how to calculate total score with SCORM and React?

1.3k views Asked by At

There are very few example on SCORM implementation, so I am a little lost. I need to get the student score based on 3 questions. I don't really understand what i am doing. I know i probably need a function like calculateScore(). SCORM is confusing the crap out of me though. I use SCORMCLOUD to test but i have reupload the build everytime i wanna test something..

APP.js

function App() {
  Scorm.init();

  const [learnerName, setLearnerName] = useState(`${Scorm.getLearnerName()}`);
  const [assessment, setAssessment] = useState([]);

  const finish = () => {
    Scorm.finish();
  };

  const updateAssesment = (correct, response) => {
    setAssessment(assessment.concat([correct]));
    Scorm.submitMCQ(correct, response);
  };
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Learner name={learnerName} />
      </header>
      <main>
        <Mcq result={updateAssesment.bind()} question="What is 10 * 10?" correctAnswer={0} answers={["100", "20"]} />
        <Mcq
          result={updateAssesment.bind()}
          question="What is the capital of Spain?"
          correctAnswer={2}
          answers={["Barcelona", "Lisbon", "Madrid"]}
        />
        <Mcq
          result={updateAssesment.bind()}
          question="Which US President's office commissioned the creation of SCORM?"
          correctAnswer={3}
          answers={["Donald Trump", "Barack Obama", "Ronald Reagan", "Bill Clinton"]}
        />
        <CompleteButton completeActivity={finish.bind()} />
      </main>
    </div>
  );
}
export default App;

SCORM.js

import { SCORM } from "pipwerks-scorm-api-wrapper";

let Scorm = {
  init() {
    SCORM.init();
  },

  getLearnerName() {
    return SCORM.get("cmi.core.student_name");
  },

  submitMCQ(correct, response) {
    let nextIndex = SCORM.get("cmi.interactions._count", true);
    SCORM.set("cmi.interactions." + nextIndex + ".id", "round_" + nextIndex);
    SCORM.set("cmi.interactions." + nextIndex + ".type", "choice");
    SCORM.set("cmi.interactions." + nextIndex + ".student_response", response);
    SCORM.set("cmi.interactions." + nextIndex + ".result", correct);
  },

  calculateScore() {
    //something here??
    SCORM.set("cmi.core.score.raw", "0");
    SCORM.set("cmi.core.score.max", "100");
    SCORM.set("cmi.core.score.min", "0");
  },

  finish() {
    alert("you have finished!");
    SCORM.set("cmi.core.lesson_status", "completed");
    SCORM.save();
    SCORM.quit();
  },
};

export default Scorm;

MCQ component

export default function Mcq(props) {
  const [selectedOption, setSelectedOption] = useState(0);
  const [answered, setAnswered] = useState(false);

  const handleOptionChange = (changeEvent) => {
    setSelectedOption(Number(changeEvent.target.value));
  };
  const renderAnswers = () => {
    return props.answers.map(function (answer, index) {
      return (
        <div className="answer" key={index}>
          <input type="radio" value={index} checked={selectedOption === index} onChange={handleOptionChange} />
          <label>{answer}</label>
        </div>
      );
    });
  };
  const handleFormSubmit = (formSubmitEvent) => {
    formSubmitEvent.preventDefault();
    setAnswered(true);
    props.result(selectedOption === props.correctAnswer, props.answers[selectedOption]);
  };
  const currentState = () => {
    if (!answered) {
      return (
        <form onSubmit={handleFormSubmit.bind(this)}>
          {renderAnswers()}
          <button className="btn btn-default" type="submit">
            Submit
          </button>
        </form>
      );
    } else {
      return <div>{checkCorrectAnswer()}</div>;
    }
  };
  const checkCorrectAnswer = () => {
    if (selectedOption === props.correctAnswer) {
      return `yes, ${props.answers[props.correctAnswer]} is the correct answer.`;
    } else {
      return `You answered ${props.answers[selectedOption]}. Sorry, but the correct answer is ${
        props.answers[props.correctAnswer]
      }.`;
    }
  };

  return (
    <div className="Mcq">
      <p>{props.question}</p>
      {currentState()}
    </div>
  );
}
2

There are 2 answers

2
Alan Plum On BEST ANSWER

Your question is a bit confusing. SCORM is an API tying two things together:

  • learning content, usually in the form of one or more so-called SCOs (i.e. HTML files with assets talking to the SCORM JS API)
  • an LMS serving the learning content and providing the SCORM JS API instance

It looks like you are trying to create learning content, although you provide a way to change the learner name, which is read-only data provided by the JS API.

If that's what you're trying to do, it's not up to SCORM to tell you the score but to you. Based on your code I'm guessing you want each question to be worth a number of points (or at least just one) if answered correctly and the score should be based on that and the total number of questions.

Since you're storing all answers in cmi.interactions already, you could try getting the number of interactions (i.e. answers) first. This tells you the total number of answers and thus the maximum number of points, or cmi.core.score.max.

Since the lowest possible score is zero points if you do a simple tally instead of something more complicated like negative points for incorrect choices being selected, the cmi.core.score.min would then be 0.

The cmi.core.score.raw would be the total number of points, so the number of answers that are correct. You could get this by iterating over the values of cmi.interactions.n.result you set previously (with n being the index between zero and cmi.interactions._count and only counting those where the result is "correct").

However note that submitMCQ should set cmi.interactions.n.result to "correct" or "incorrect" not true or false. A conforming LMS will otherwise reject the value as invalid (which does not raise an exception but instead sets an error flag you have to check explicitly because SCORM is weird like that).

0
Mark On

I designed SCOBot Content API to assist with some of this if you want to give it a shot. Least if you get tired of trying to look up all the individual calls.

It adds the extra wrapping to manage total interactions / scoring calculations so you're not writing all the extra management of it.

https://github.com/cybercussion/SCOBot/blob/e67239c36fdc104be9d29b0810815f9b3175c831/QUnit-Tests/js/scorm/SCOBot.js#L841

And example grouping so you can define it once and it takes care of it - https://github.com/cybercussion/SCOBot/wiki/SCORM-SCOBot-Documentation#set-interaction

There are many config settings to turn the 'bot' part of the project on/off etc...

QUnit tests were also written in the test directory which show several interactions/objectives and other calls made. https://github.com/cybercussion/SCOBot/blob/e67239c36fdc104be9d29b0810815f9b3175c831/QUnit-Tests/js/test/scobot-prod.js#L252

I also designed it to work independent of 1.2 / 2004 so you're using one set of namespaces. And not having to remember all the version differences.

This project is open source, free to use/test out. Latest version broke it out of jQuery so it has its own event system and utilities. If you use something like Angular, you may have to work with NgZone, React I think you may be ok. If you run into an issue let me know.