Skip to content

Frontend Introduction

Craig Jennings edited this page Jul 6, 2018 · 7 revisions

Tools

  • Backbone with Marionette
  • jQuery
  • Webpack w/ Babel plugin (for ES6 support)
  • Handlebars (templating engine)
  • Selectize
  • Moment.js
  • Bootstrap
  • Many other smaller plugins (view the package.json file for a complete list)

Code Style

We use ES6 syntax in Agent. It is transpiled down to ES5 using Babel. In order to better understand the code that you'll read in this tutorial, please familiarize yourself with the new features an syntaxes of ES6. Some helpful resources to do this are:

Architecture

Modules

We use the MVC architecture to drive our modules

Model (The Data)

  • The data that drives a View
  • One-to-one relationship with a View
  • Should not contain knowledge of a View
    • Meaning no DOM manipulation or interaction

View (The UI)

  • Takes a model's data and builds a UI around it
  • Handles user interaction with the UI

Controller

  • Connects the View & Model, and usually renders the initial view.
  • Almost always contains a show method
    • The first parameter should be a Marionette Region object where the module should be rendered
    • Any required information for the module should extra parameters after the region
    • Any optional information should be contained in an options hash that is the last parameter of the show method
    • Can return the model of the module to be leveraged for communication by the caller

An Example

First, a list of dependencies for the module

// Use ES6 imports
import app from 'app';
import errorPageTpl from 'hbs!templates/error/errorPageTpl';

Now define our Model. Notice that all we need is to set some defaults for this one. Models can contain more complicated code, but should only handle data computation, and should never be aware of the DOM or View that owns it.

const Error = Backbone.Model.extend({
  defaults: {
    consoleUrl: `${app.base}console`,
  },
});

Then define our View. This is a very simple view that only uses a template. Views can get much more complicated in order to handle user interactions with the UI.

const ErrorView = Marionette.ItemView.extend({
  className: 'container',
  template: errorPageTpl, // Notice this is a dependency that we've pulled in
});

Let's take a break from Javascript for a second and look at our Handlebars template. This is a very simple template, but shows the basics of using Handlebars.

<div class="alert alert-error error-page">
  <h3>{{ domain }} {{ id }} does not exist</h3>

  <p><a href="{{ consoleUrl }}">Return to the console</a></p>
</div>

See that we simply surround the Model attributes with double curly brackets, ({{ attribute }}) when we want to render that value.

Next, define our Controller. This can be a simple Javascript Class. Consider this the API for this module that other modules will use. We've defined our show method that takes the region first, but then also requires a domain and id. Since there are no optional parameters for this module, there is no options hash parameter.

class Controller {
  show(region, domain, id) {
    // Construct our model with the given data
    const model = new Error({ domain, id });

    // Create our view and pass the model to it
    const view = new ErrorView({ model });

    // Render the view in the desired region
    region.show(view);

    // Return the model so that the caller can listen in on model events if desired
    return model;
  }
}

Lastly, we need to export our controller so that other files can import and use it.

export default Controller;

Now that we have a module, let's use it:

import ErrorPage from 'app/core/ErrorPage';

// This region points to where we want the error page to be shown
var region = new Marionette.Region({ el: 'body' });

// Create a new instance of the ErrorPage class and call 'show' on it.
const errorPage = new ErrorPage();
errorPage.show(region, 'My Domain', 123);

The resulting errorPage would render as:

<div class="alert alert-error error-page">
  <h3>My Domain 123 does not exist</h3>

  <p><a href="/console">Return to the console</a></p>
</div>

Regions

Regions are the driving force of the UI layout. They are aptly named because they represent areas in the layout that a module should be rendered. Regions make module re-use very easy because simply passing in a different region to the module allows it to render in a different part of the app. Let's look back at our previous example to get a better understanding of this:

const region = new Marionette.Region({ el: 'body' });

The el value in the hash indicates what element in the DOM this region should be associated with, in this case the body of the page. Then we passed that region object down to the errorPage module. Inside the show method, we had

// Create model and view
...
region.show(errorView);
...

Here the errorPage module is telling the given region to render it's created view. What if we wanted to render this error page in a different DOM element?

const region = new Marionette.Region({ el: '.my-other-error-page' });

errorPage.show(region, 'My Different Domain', 456});

Now the errorPage's view will be rendered inside the .my-other-error-page DOM element.

LayoutViews

LayoutViews are simply Marionette ItemViews with Regions mixed in. This makes for easily creating Regions associated with certain DOM elements in your Layout's template. Let's take a look:

const MyLayoutView = Marionette.LayoutView.extend({
  // A basic template with two empty divs. I want to render child modules inside
  // these divs. The '_.template' is simply a helper function
  template: _.template(`
    <div class="first"></div>
    <div class="second"></div>
  `),

  // Create regions in the LayoutView that are associated with DOM elements in
  // the template
  regions: {
    firstRegion: '.first',
    secondRegion: '.second',
  },

  onRender() {
    // We render sub-components during the rendering of the layout view
    otherModule1.show(this.firstRegion);
    otherModule2.show(this.secondRegion);
  },
});

class Controller {
  show(region) {
    const myLayoutView = new MyLayoutView();

    region.show(myLayoutView);
  }
}

Next: Routing

Clone this wiki locally