9. Developing User Interfaces

This section provides information about developing user interfaces (UI) for edX applications.

All new edX pages should be composed of elements and styling shown in the edX Pattern Library. You can use JavaScript from the edX UI Toolkit JavaScript library to implement pages in a way that is consistent with edX UI architecture.

The Open edX wiki includes additional details about developing edX user interfaces. For more information, see edX Front End Development.

9.1. Using the edX Pattern Library

The edX Pattern Library is a collection of web application components and visual styles that you can use to develop UIs that are consistent with the edX platform.

The edX Pattern Library is available at ux.edx.org.

The edx-platform GitHub repository includes an example web page that demonstrates using the Pattern Library. You can use the example page as a template for creating your own new LMS or Studio pages.

To create a new page using the Pattern Library, you use the same main.html template as for non-Pattern Library pages, and pass the following context parameters.

{
    ...
    'disable_courseware_js': True,
    'uses_pattern_library': True,
}

When you create a page using the Pattern Library, create two new root syntactically awesome style sheets (Sass) files for your page, one for left-to- right (LTR) and one for right-to-left (RTL) localizations. For more information about Sass, see the Sass site.

The RTL version of the style sheet must have the same filename as the LTR version but ending with -rtl.

Register the two new style sheets in the PIPELINE_CSS configuration. You can see the full list for the LMS in lms/envs/common.py.

After you register the LTR and RTL style sheets, you refer to the configuration name in your Mako template. Mako will then dynamically pick either the LTR or the RTL version based upon the current user’s localization preference.

The following example shows how a pair of style sheets are registered in the PIPELINE_CSS configuration.

'style-learner-dashboard': {
    'source_filenames': [
        'css/lms-learner-dashboard.css',
    ],
    'output_filename': 'css/lms-learner-dashboard.css',
},
'style-learner-dashboard-rtl': {
    'source_filenames': [
        'css/lms-learner-dashboard-rtl.css',
    ],
    'output_filename': 'css/lms-learner-dashboard-rtl.css',
},

After you register your Sass style sheets, you must also declare the name of your Sass files at the top of your main Mako file. The following example declares files that are configured with the names style- learner-dashboard and style-learner-dashboard-rtl.

<%! main_css = "style-learner-dashboard" %>

You can refer to the following examples.

9.2. Using the edX UI Toolkit

The edX UI Toolkit is a JavaScript library for building edX web applications. The UI toolkit consists of:

  • Backbone views to implement patterns as defined by the edX Pattern Library.
  • Utility methods to simplify the creation and testing of user interfaces.

The edX UI Toolkit is available from the edx-ui-toolkit GitHub repository . For more information about the UI Toolkit, see ui-toolkit.edx.org .

For more information about writing JavaScript for edX UIs, see EdX JavaScript Style Guide.

9.3. Adding a UI Page

This topic is a collection of things to think about when creating a new page in Studio or the LMS.

You should also read Contributing to Open edX to understand more general best practices, as well as how to submit your new code for review.

9.3.1. Use JavaScript Instead of CoffeeScript

The edX front end code base is currently a mixture of CoffeeScript and JavaScript. The edX development team is now standardizing the use of JavaScript for new work. There are a number of reasons for this decision, including the following list.

  • It is hard to jump back and forth between the two syntaxes.
  • It adds a compilation step which slows down iterative development.
  • Most third party libraries are implemented and documented in JavaScript.
  • Client-side debugging is done in JavaScript, which makes it hard to correlate to the original code.
  • The diff-cover tools are not compatible with CoffeeScript so code coverage is not tracked.
  • A lot of the traditional benefits of CoffeeScript can now be achieved using JavaScript libraries.

All new front end development should be done in JavaScript, while legacy CoffeeScript code will be converted on an as-needed basis. For pragmatic reasons, targeted changes to CoffeeScript will still be accepted.

For more information about writing JavaScript for edX UIs, see EdX JavaScript Style Guide.

9.3.2. Use Webpack to Bundle Dependencies

Write your JavaScript as an ES2015 module. There should be one root file per feature (your entry point, sometimes called a “factory”) which may require other modules.

import ReactDOM from 'react-dom';

import { Component } from 'edx-ui-toolkit/components';

export class Feature {
  constructor(domNode) {
    ReactDOM.render(<Component fooProp={ foo } />, domNode);
  }
}
  • Use a non-default export for the function or class that you will call to instantiate the factory. Django-webpack-loader cannot currently load a default export.
  • Add your module to the Webpack config’s entry object.
  • Load your Webpacked module from your server-side templating system. If using Django (or Jinja) templates, you can use the template tags provided by Django-webpack-loader.
  • If using Mako in edx-platform, use the Webpack entry point loader from static_content.html. This wrapper is necessary because Django-webpack-loader ships template tags for Django and Jinja templates only. For an example, see the Course Outline. (Note: These files may be updated as our use of Webpack evolves, check both the specific linked SHA and the most recent versions for more details.)
  • Webpack can also be used to bundle AMD modules previously managed by RequireJS without rewriting the code, as it has native support for a variety of module specs. ES2015 modules should be preferred for new code, especially any ES2015 code, but the benefits of bundling code with Webpack are such that we recommend moving actively maintained AMD ES5 out of RequireJS to Webpack whenever possible.

9.3.3. Adding JavaScript Libraries

Install JavaScript libraries using the npm package manager.

If the library you want to add is available from npmjs.com, update edx- platform’s package.json file to reference the library. Use the tilde ~ prefix for the version to allow patches to be picked up automatically.

For more information about versioning, see semver.org.

Execute the following command to install your library.

paver install_prereqs

Add the new library to the list of npm-installed libraries.

Execute the following command to make your library available as a Django static asset (choose LMS, Studio, or both).

paver update_assets lms --settings=devstack

To add the new library to the LMS, follow these steps.

  1. Add the library to the lms/env/common.py file at https://github.com/edx /edx-platform/blob/master/lms/envs/common.py#L1246
  2. Add the library to lms/static/lms/js/require-config.js.

Add the new library to the list of vendor libraries that are installed by update_assets. Update the variable NPM_INSTALLED_LIBRARIES in /pavelib/assets.py.

Note

Reference the unminified version of the library. This allows debugging tools to step through readable library code, and the Django static asset pipeline will ensure that it get optimized for production.

If you cannot use npm, check the file into the common/static/js/vendor directory.

9.3.4. Use React or Backbone to Build Your New UI

Code written with these frameworks is more modular, more extensible, and more easily unit tested with Jasmine. React is the suggested framework for all greenfield development. Development in Backbone will be deprecated in the future. See the React rollout plan for more details.

See How to add a new feature to LMS or Studio to see how to structure your code.

9.3.5. Use Underscore for Backbone Client-Side Templates

The edX development team has standardized using Underscore for client-side templates used with Backbone. There are some things to consider.

  • Add your templates to a static/templates directory in your Django app.
  • If you need mock HTML for Jasmine, put them in a subdirectory named mock.
  • Use RequireJS Text or a Webpack loader to load your templates (note that most code today statically includes the template in the page).

Using either of these bundlers is superior to the old mechanism of statically including the templates in the Mako-generated HTML for several reasons.

  • Mako templates no longer need to explicitly include every template that is needed in the HTML.
  • The template lookup is much faster when optimized for production use because JavaScript code does not have to extract the templates it needs from the DOM. For developers, the templates are fetched asynchronously like any other dependency.
  • The templates get included in the minified .js file for the page so the content is only downloaded once and then cached in the browser. Including the template in the Mako template causes it to be downloaded every time.
  • Unit tests do not need to load all the templates into fixtures for the views to work, as the view will load its template dependencies directly.

Here are a few examples.

9.3.6. RequireJS Use in Legacy Code

RequireJS is a dependency management system used widely at edX before the introduction of Webpack. RequireJS may still be necessary when dealing with legacy code, especially code not written as AMD or ES2015 modules. However, the current recommendation is to use Webpack for all new code and to transition AMD modules using RequireJS to Webpack as development occurs.

Here are some notes on how RequireJS has been implemented in the past.

  • The AMD syntax should be used to load JavaScript dependencies in RequireJS.

    Note

    For the LMS, you have to wrap each call to define or require in order for it to work with the namespaced version of RequireJS.

    (function(define) {
        'use strict';
    
        define([...], function(...) {
            ...
        });
    }).call(this, define || RequireJS.define);
    
  • Use RequireJS Text for templates loaded through RequireJS (see Ensure That RequireJS Optimizer Can Optimize Your JavaScript for details).

  • Factory classes should be included by your Mako template using require_module.

    • This Mako function ensures that the factory is loaded correctly for both the optimized and non-optimized pipeline.
    • When accessed in the optimized mode, the query parameter ?raw will be added to the URL to ensure that the file is not post-processed.

    Note

    require_module is needed for the unified optimizer pipeline in Studio.

  • If you need server-side data passed to your page, pass it as one or more parameters to your factory.

    For information about avoiding cross-site scripting, see JavaScript Context in Mako.

  • For information about adding third-party JavaScript libraries, see Ensure That RequireJS Optimizer Can Optimize Your JavaScript.

  • If the dependency is pre-loaded, then be sure to specify its path as empty in build.js so that the optimizer does not include it in the bundled file. This is especially important for files that are used by both by JavaScript leveraging RequireJS and “non-RequireJS JavaScript” within the same page.

    paths: {
        'gettext': 'empty:',
        'jquery': 'empty:',
        ...
    }
    

See an example Mako code block in JavaScript Context in Mako.

The following list includes more examples.

Note

Using RequireJS in XBlocks is not currently supported. Future development is planned to add this support.

9.3.7. Ensure That RequireJS Optimizer Can Optimize Your JavaScript

We use RequireJS Optimizer to optimize some JavaScript in production. Specifically, the RequireJS Optimizer (sometimes called r.js) bundles and optimizes JavaScript which uses RequireJS for its dependency management. This works as part of the production pipeline to merge all of a page’s JavaScript and templates into a single minified file. This greatly reduces the number of files that the browser needs to request, as well as the number of bytes that need to be fetched. Furthermore, in the LMS un-optimized RequireJS files are never cached, leading to significant performance degradation.

The basic approach is to move all of the page view construction logic into a single factory file. This factory is responsible for creating the models and views required to render the page. The idea is that the optimizer can produce a single minified file for the factory by statically determining all of its dependencies.

If using RequireJS (as opposed to Webpack), follow these instructions to make sure the RequireJS Optimizer can optimize your modules.

  • Structure your page so that it has a single root factory file (see Use Webpack to Bundle Dependencies).

    Be sure to use require_module to load the factory as described above.

  • List your factory in the RequireJS Optimizer build file.

  • By default, devstack runs with unoptimized files so that changes are picked up immediately.

    To run with optimized files, specify the --optimized parameter to Paver’s devstack command:

    paver devstack lms --optimized
    
  • Note that Bokchoy tests run with optimized files to verify that they are being generated as expected.

  • Be sure to test your page in a production environment, checking that all dependencies are included in the optimized factory file.

    • For LMS files, take care that all of the JS files have a MD5 hash code between their filename and the js extension. Files without MD5 hash codes will not be cached in production and indicate incorrect optimization.
    • Studio RequireJS files currently do not use the MD5 hashing mechanism, and instead store files within a unique directory that changes with each release. We hope to change this in the future.