Digital badge building with reusable Web Components

David Douglas

iDEA initiative

The Microsoft UK DX team have been working closely with the Partner Catalyst team on the Duke of York’s Inspiring Digital Enterprise Award (iDEA) scheme. The initiative aims to help young people learn and develop digital skills. There are four core categories of online challenges and each category contains its own series of digital badges that consist of text, graphics or video content with quiz elements. When successfully completing a badge, young people win points towards unlocking nationally recognised awards. The first of these awards, the iDEA Bronze Award has just launched; Silver and Gold Awards are coming in the future as more badges become available.

Badge development challenges

In order to launch such an initiative, there would need to be a considerable body of work done in conjunction with various content providers and developers to create the number of badges required to win an Award. Initially, all content providers had to develop each badge by themselves, which put the burden on them to build the quiz components and link back into the system. The other problem was that not all the content providers had a team of web designers and developers available to develop the badge.

Microsoft was already supporting iDEA with Azure, and now saw a way to make the process easier for content providers who do not have the in-house capability or resource to create bespoke badges.

To accelerate the number of badges created and published on iDEA, our Partner Catalyst team at Microsoft decided to help by developing a badge builder to author content and quiz elements. We wanted to lighten the development burden and reduce the creation time for badges so that non-developers could build one easily and quickly. The badge would also link into the iDEA authentication system and assist in the publication of new badges on the platform.

This article explains the reasons for choosing Web Components, how we used them dynamically, as well as the backend features that were used in building the project, which is shared on GitHub.

Designing for ease of use

We had an initial envisioning session with the office of HRH the Duke of York to determine the shape of the badge builder. Essentially, we would need some kind of CMS (Content Management System) to create, edit and manage all the badges. Each badge would display content and media elements, and also incorporate interactive elements into its quiz. For an optimal user experience, we determined the interface should consist only of the functions required to build a badge, with no extraneous menu options or clutter. However, this choice meant that an out-of-the-box CMS would not suit our specific requirements.

In order to develop our own badge builder, we had to define the types of elements needed for this purpose, which we would package as reusable Web Components. This approach would also help us ensure a consistent and predictable interface across multiple badges.

https://www.youtube.com/watch?v=l3HhTd1YhIc&rel=0

Development process

The fundamental design requirement for the badge builder and viewer was to provide a good user experience and utilize responsive design. It was therefore key to use a responsive grid layout and mobile friendly UI. The client-side web app would handle all the rendering of the dynamic content for editing and viewing purposes. In terms of the server side, we just needed to expose an API to hook up with the database to save and load the badge content as well as handle user auth and submissions.

Bearing these requirements in mind, we utilized the following technologies in our solution:

Polymer – a mobile friendly UI kit for web

Polymer provides a responsive Material Design UI kit consisting of Paper and Iron elements for building web apps which can be imported separately for use. We also used Polymer to make our own custom Web Components.

The Polymer Starter Kit (PSK) is a good way to start a project and saved time by setting up a stack of things for us like the Node and Bower dependencies. The PSK boilerplate is now available through Polymer-cli.

SASS and Foundation Grid – rule based responsive design

We used SASS and Foundation Grid for responsive design. Nobody wants to edit a monolithic CSS file with hundreds of styles, not to mention deal with the conflicts that will happen in source control. SASS can reduce the physical line count that you edit, and you can split it into separate files which can be imported making it easier to work with especially in source controlled projects.

SASS makes it easy to import the popular Foundation Grid module for responsive design without loading all the other parts of Foundation. We also used Mixins for generating repeatable chunks of code required for responsive design including high definition image replacement using media queries.

Nodemon and BrowserSync – fine tuning and design work

Because even small design changes can be quite time-consuming, I find live reload essential to quickly fine tune the interface and user experience when designing and developing across multiple screens and browsers. We used Nodemon and BrowserSync for live reload of server and client. It triggers an automatic refresh in the browser when any changes are made on server or client side.

Express and Document Database – save and load dynamic content

Express was used for the server-side APIs and requests are made from the client using Polymer’s iron-ajax element. The badge content is saved as JSON to DocumentDB database and is easily loaded for editing and display using Polymer’s data binding.

Badge Builder

Building the backend

Features of the Node.js server

The Node.js app built on the Express framework serves multiple purposes.

Serves the Polymer app:

var rootPath = path.join(__dirname, '..', appFolder);
var staticFolder = '/static';
app.use(staticFolder, express.static(rootPath));

Authenticates against Auth0:

var strategy = new oauth2.Strategy({
  authorizationURL: 'https://' + process.env.AUTH0_DOMAIN + '/i/oauth2/authorize',
  tokenURL: 'https://' + process.env.AUTH0_DOMAIN + '/oauth/token',
  clientID: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET,
  callbackURL: process.env.AUTH0_CALLBACK_URL,
  skipUserProfile: true,
  state: true
}, function (accessToken, refreshToken, profile, done) {
  // Extract info from JWT
  var payload = jwt.decode(accessToken);
  done(null, {
    id: payload.sub,
    accessToken: accessToken
  });
});

Implements the REST API. For example, here is how getting an individual badge was implemented:

var mongoose = require('mongoose');
var models = require('../models/models.js');
var router = express.Router();

router.get('/badges/:id', requireUser, function (req, res) {
  try {
    var _id = mongoose.Types.ObjectId(req.params.id);
    models.Badge.findOne({ _id: _id }, function (err, badge) {
      if (err) {
        return sendError(res, err);
      }
      res.json(filterBadge(badge));
    });
  } catch (err) {
    sendBadRequest(res);
  }
});

A challenge for us in the authentication process was that the badge builder hosts multiple badges that each need to represent themselves towards the Auth0 servers as individual OAuth consumers. To do that, we implemented logic to use consumer key and secret based on the API route the request comes to. Below is a snippet that shows how the key and secret are picked up from the database in which they are stored while the badge is created using the builder:

app.use(['/badges/:id', '/api/badges/:id'], function (req, res, next) {
  models.Badge.findById(req.params.id, function (err, badge) {
    if (badge.consumerKey && badge.consumerSecret) {
      // If badge has been setup with custom Auth0 consumer,
      // update the request with the right values.
      req.oauth2.clientID = badge.consumerKey;
      req.oauth2.clientSecret = badge.consumerSecret;
    }
    next();
  });
});

We are using Azure App Service to host the Node.js backend. This allows us to scale the system when needed. For starters, we selected pricing tier S1 Standard that allows also a custom domain which is used in production. An approximation of the monthly cost can be seen below:

App Service Cost

Choosing the database

When considering the database options, we wanted to use something that is low-effort to maintain in Azure and easy to develop against. There are good client libraries and object document mappers for MongoDB and we chose mongoose to meet the requirement for ease of development.

To meet the requirement for low-effort maintenance, we chose the Azure DocumentDB and specifically, the MongoDB protocol support that is built in. After provisioning the database using the Azure portal, we got a secure-by-default and production-ready NoSQL database without having to manage any servers ourselves. By selecting relatively moderate throughput and storage options, we were able to achieve a cost-efficient solution. Since DocumentDB supports dynamically adjusting throughput and storage, we knew that it would be easy to scale the system afterward, as needed.

When the system is in use, 4 collections end up being provisioned. Below is an approximate monthly cost from the database when using the minimum throughput level (400 Request Units):

DocumentDB CostDuring development, we used locally installed MongoDB instances that are available cross-platform; switching to use DocumentDB in production is just a matter of changing the connection string in an environment variable. The local MongoDB instances provided each developer an isolated development environment with zero cost. For Windows users, there is also an emulator to run DocumentDB locally.

You can find the architecture for badge builder illustrated below: Badge builder architecture

Building the Web Components

After the envisioning session and hack we rolled out the rest of the quiz and content components for building badges.

Quiz components

  • Single choice to select the correct answer from two or more options
  • Multiple choice to select any correct answers from several options
  • Groups to drag and drop options into their correct groups
  • Ordered list to drag and drop options into their correct order
  • Keywords to type exact keywords to match an answer
  • Comments to type a number of words required to provide an answer

Content components

  • Section to split a quiz into progressive steps
  • HTML to add content with HTML formatting
  • Video to embed a video player
  • Link to link to additional resources

To create reusable Web Components you can start with the Polymer Seed Element which sets up test, demo and documentation pages. But rather than have the initial overhead of managing and publishing multiple custom elements during development, it was faster to bundle our own with the project.

If you are new to Web Components, it’s worth pointing out the common pitfall of incorrectly naming custom components: remember custom element names need to be hyphenated (e.g. “my-element”).

All the Web Components for our badge builder needed to provide two different views: an edit view for the editor and a quiz view for the interactive badge itself.

Edit View

Edit view

Quiz View

Quiz view

At the time, we opted to use the same Web Component for both views with the Polymer dom-if template to render the parts unique to each view in this case. However, it would be perfectly valid to split the badge elements into two separate Web Components to render each view.

Loading Dynamic Web Components

The quiz content was loaded with Polymer’s iron-ajax element, and the array of content was parsed in the response handler using a switch statement to check against specific element types.

for (var i=0; i<elements.length; i++) {
  var element = elements[i];
  switch(element.elementType) {
    case "content-html":
      this.addHTML(element.text);
    break;
    case "content-video":
      this.addVideo(element.embededURI);
      break;
    case "content-button":
      this.addButton(element.buttonURL, element.buttonText);
      break;
    case "content-section":
      this.addContentSection(element.title);
      break;
    case "quiz-short-input":
      this.addQuizShortInput(element._id, element.question, element.answerKeywords, element.answer, element.hintText, element.showHint);
      break;
    case "quiz-long-input":
      this.addQuizLongInput(element._id, element.question, element.answer, element.wordLimit, element.hintText, element.showHint);
      break;
    case "quiz-list-groups":
      this.addQuizListGroups(element._id, element.question, element.answer, element.hintText, element.showHint);
      break;
    default:
      this.addQuizElementType(element.elementType, element._id, element.question, element.options, element.answer, element.hintText, element.showHint);
      break;
}

Most elements are unique and are handled separately, apart from the default case, which is for elements that share exactly the same object properties. In this case, the element type is passed to the function to create the element and set the properties by using the document.createElement method.

Once the element has been created, and its properties are set, it still needs to be added to the DOM. The dynamic components are added to a placeholder div using the appendChild(element) method.

<div id="components"></div>
addElement: function (element) {
  element.edit = true;
  element.classList.add("draggable");
  Polymer.dom(this.$.components).appendChild(element);
}

Notice that we can use Polymer’s dollar selector to append children to our div tag with the id="components" attribute. Because we are directly manipulating the DOM by adding elements dynamically, it is necessary to wrap the selector using the Polymer DOM API.

One usability tweak is to have the page scroll down to show a newly added component when adding them. The problem with scrolling down here is that height of the new element will not be known until the DOM has updated, so we will need to add a listener to handle the dom-change event in our Polymer component. Now the page will automatically scroll down to show the new element we have added.

scrollDown: function(){
  window.scrollTo(0, document.body.scrollHeight);
},
 
listeners: {
  'dom-change' : "scrollDown"
}

Saving dynamic content using Web Components

To save the dynamic content for each element, I would need to be able to get the content as JSON. A nice way to handle this for all components is to use a shared behaviour, which would hold the _id property assigned by the database and also assign the element’s type using the built-in this.localName property.

  window.QuizBehaviors = window.QuizBehaviors || {}; // Behavior namespace
  QuizBehaviors.DataModelBehaviorImpl = {

    properties: {
      /**
       * The `id` of the element for database
       * @type {string}
       */
      _id: {
        type: String,
        value: function() {
          return "";
        }
      },


    },

    /**
     * Returns the JSON data model for saving. 
     * NB: To capture more properties the `getData` method can be overridden by the custom element.
     */
     getData: function() {
       return {
         _id : this._id,
         elementType : this.localName
       };
     }

  };

  QuizBehaviors.DataModelBehavior = [
    QuizBehaviors.DataModelBehaviorImpl
  ];

Finally, when changes need to be saved, it’s just a case of returning a list of all our custom elements and grabbing the data as JSON using the element’s getData behaviour. This data array can then be posted using Polymer’s iron-ajax element for saving to the database.

Making badges fun with the badge builder

The platform launched with 32 new badges and anyone can sign in to take part in the programme. It was great to see the Palace create their first Gaming Badge using the badge builder and include additional animation scripts to bring the badge’s content to life. The animations added an element of fun to the learning process and suited the gaming nature of the content. Polymer’s neon-animation behaviors could also be plugged in to animate the progress throughout the badge sections.

The badge builder project can be extended by adding additional content and quiz components over time, allowing other types of challenges to be introduced as Silver and Gold Awards become available.

If you’re a web developer and haven’t tried developing Web Components yet, I hope this article will encourage you to start making use of them. Polymer elements coupled with your own Web Components will provide you with a solid set of reusable blocks for building responsive web apps quickly.

0 comments

Discussion is closed.

Feedback usabilla icon