Challenger.js

4 min read Original article ↗

Pop-up JavaScript challenges in your browser

  • Try thesedemos:

Challenger is a drop-in JavaScript library that adds interactive programming challenges to any page. Challenges are flexible and expressive, and are super simple to write.

A challenge has requirements based on code structure and program output, and gives users a code editor to experiment in. When new code is written, it's run in a sandbox and the output is analyzed.

Challenges can be presented as one-off tests or linked together to form courses.

Usage

Writing challenges

Challenges are defined as simple JavaScript objects. The following optional keys can be set:

  • title— the challenge's title

  • description— a short description of the challenge

  • initialCode— text to be loaded into the code editor on initialization

  • whitelist— an array of ParserAPI strings, indicating expressions that must exist in the student's code

  • blacklist— same as whitelist, but these expressions must not exist in the student's code

  • nestedRules— an object indicating nested requirements. Naming conventions are consistent with the whitelist and blacklist, but each expression key can take another nested object as its value. Setting the required attribute on an expression indicates when the expression is required or disallowed. For example:

    // indicates that the student must include an if statement within a while
    // statement, but must not include a for statement within a while statement.
    var challenge = {
      // ...
      nestedRules: {
        WhileStatement: {
          IfStatement: {
            required: true
          },
          ForStatement: {
            required: false
          }
        }
      }
    };
  • customRules— an array of objects describing custom rules. Each object must have two keys:

    • description— a string that describes the rule

    • verify— a function that takes the current user input as a string and returns a boolean

    For example:

    var challenge = {
      // ...
      customRules: [
        {
          description: 'Program must have the word \'foobar\' in it',
          verify: function (str) { return str.indexOf('foobar') > -1; }
        },
        {
          description: 'Program must be under 140 characters',
          verify: function (str) { return str.length < 140; }
        }
      ]
    };
  • output— an object specifying the program's expected behavior. The following keys can be set:

    • description— a string that describes the rule

    • setup— a string of code to run before the user's code

    • verify— a function that accepts non-object, non-function arguments and returns a boolean. It is aliased to __verify__ in the challenge, and can be called from setup and teardown

    • teardown— a string of code to run after the user's code

    For example:

    // outer variables
    var unsorted = [6, 1, 8, 6, 7, 2, 4, 3, 1];
    var expected = unsorted.sort((a, b) => a > b);
    
    var challenge = {
      // ...
      output: {
        description: 'Program must sort an array of ints called "unsorted" in ' +
                     'ascending order and save it to a variable called "sorted"',
        setup: `var unsorted = [${unsorted}];`,
        verify: (...sorted) => sorted.every((n, i) => n === expected[i]),
        teardown: 'if (Array.isArray(sorted)) __verify__(...sorted);'
      }
    };

    Note: the above snippet uses features of ECMAScript 6. Good news: users can write their challenges using ES6 and it will be transpiled to ES5 before running!

Running challenges

To load a single challenge, pass its object into the challenger function:

var challenger = require('challenger');

var newChallenge = {
  title: 'My new challenge',
  description: 'It\'s all about the Pentiums',
  whitelist: ['DoWhileStatement']
};

challenger.loadCourse(newChallenge);

To create a course out of multiple challenges in sequence, pass an array of challenges into the challenger function:

// ...
challenger.loadCourse([challenge1, challenge2, challenge3/*, ... */]);

The challenger function can accept an options object as its last argument. The following keys can be set:

  • parent— DOM Node to insert the challenge into. Defaults to document.body

  • onExit(success)— a callback function to run when the challenge is closed; success is boolean

  • successText— a string to display when the user is successful

Installation

To install the library, use npm or bower:

bower install challenger # or...
npm install challenger

Challenger is also hosted on GitHub and cdnjs.

All necessary files exist within the /client/dist/ directory:

  • challenger.min.js— the main library file

  • challenger.min.css— the base stylesheet. Even if you plan on restyling the challenges, it's recommended that you include this; it will give the challenges some initial structure to work with

  • operative.min.js— helper module, required if running output rules in IE10

Sadly if you need to use IE10 with output rules, require('challenger') won't work without also copying operative.min.js to the same folder as your script. Challenger runs all client code in a sandboxed Worker or iframe, and certain components need to be broken out and re-loaded into the sandbox. For more information on how sandboxing is implemented, check out operative.

Browser support

Mozilla Firefox Google Chrome Opera Internet Explorer Safari Mobile Safari
9+ 18+ 15+ 9+ 5.1+ 7+

If you need to support older browsers, include krisowal's es5-shim along with es5-sham.js from the same repository. You might also need to tweak the CSS.