Starting React

I’ve been dabbling with the concept of a remote part-time job to provide a bit of personal funding as our business venture slowly gets off the ground, and this has highlighted the current popularity of React. It’s not like I’ve had my head completely in the sand, but the number of roles requiring a specific JS framework, rather than just JavaScript (plus a framework preference) did surprise me a little.

With all that in mind I decided to take a proper look at React, and compare it to my current front-end framework of choice, Aurelia. The fruits of this exploration are in GitHub and hopefully not too embarrassing :).

Philosophy

There is a continuum in current web libraries between being a full framework, like Angular or Aurelia, and a view library, where non-rendering activities like routing and dependency injection are not included. React sits at the view library end of this spectrum, but with well established and supported choices for the excluded activities. In this respect I’ve found React easier to learn than Aurelia because it enables the ecosystem to be learned step-at-a-time – although it could also be said that significant experience with any of these frameworks would make learning the next easier.

React is strong on conventions, although at times it has felt like these conventions obscure what is going on and make ‘magic happen’, which is not a feeling I enjoy when developing. This is particularly so with react-redux.

It also leans more towards JavaScript’s dynamic typing than a static typing style, emphasized in the event handler example which does this.setState({ [name]: value });. However it also encourages the use of typed systems like TypeScript which requires some trawling through the index.d.ts file to come to grips with.

Components

React is driven by the notion of small and composable components, and it does make creating and working with these very easy. The use of JSX/TSX files allows advanced logic to be mixed with markup giving complete flexibility over the creation of elements. There is a risk to this, which is that a component gets bloated instead of being split into smaller parts, so good code maintenance practices are important.

Styling is fairly flexible, allowing anything from basic CSS use, referenced from the index.html, through to compiled and bundled SCSS using a packing system like webpack.

Data

My basic understanding is that data that is owned by a component goes into this.state and is modified by setState(), while data that is used by a component goes into this.props and is read-only. In a basic scenario (i.e. without Redux) data flows down via properties on this.props, and up via callback methods on this.props. e.g.

// in parent component
private handleChange(data) {
  this.setState({ filtered: data});
}

render() {
  <Subcomponent all={this.all} onChange={(data) => this.handleChange(data)} />
}

// in a subcomponent method
this.props.onChange(this.props.all.filter(f => f.something > 0));

This is certainly more laborious than Aurelia’s two-way binding, something React’s documentation acknowledges: “It can sometimes be tedious to use controlled components, because you need to write an event handler for every way your data can change and pipe all of the input state through a React component.” However this approach does make data passing and ownership very explicit and therefore understandable. Going to react-redux changes all this, and that’s something I’ll leave for a future post.

So there you have it – my first couple of days building something with React.

Using aurelia-ux v0.6

aurelia-ux is the pre-release UI component kit for aurelia. It has been developed throughout 2017 including a significant re-organization between v0.3 and v0.4. Due to this reorganization and its pre-release status, there is limited information out there on how to use it, with the best example at present being aurelia-store-todo. There is an indication that documentation is on the way and hopefully as part of that the team’s app-ux-showcase will be updated past v0.3. In the meantime I decided to dig into the source a bit and build a little something to share here.

Setup

This setup assumes you have aurelia-cli running, so that you can go to a command line and type au new to create the project. Open the project folder and add the following dependencies to the package.json then run npm install (or the yarn equivalent) to get the aurelia-ux packages into your node_modules folder:

  "@aurelia-ux/components": "^0.6.0",
  "@aurelia-ux/core": "^0.6.0",

aurelia-store-todo uses WebPack as the bundler, however I will be sticking to the aurelia-cli. This means editing the aurelia.json to include the dependencies. The first dependency is @aurelia-ux/core which contains the core capabilities. Then each component needs to be added individually. There is also an @aurelia-ux/components which may be intended to include all components, but including this causes build errors as the require exports for all the referenced components are not able to be resolved. The additions to the vendor-bundle dependencies in aurelia.json look something like this:

{
  "name": "@aurelia-ux/core",
  "path": "../node_modules/@aurelia-ux/core/dist/amd",
  "main": "index",
  "resources": ["**/*.{css,html}"]
},
{
  "name": "@aurelia-ux/input",
  "path": "../node_modules/@aurelia-ux/input/dist/amd",
  "main": "index",
  "resources": ["**/*.{css,html}"]
},
{
  "name": "@aurelia-ux/button",
  "path": "../node_modules/@aurelia-ux/button/dist/amd",
  "main": "index",
  "resources": ["**/*.{css,html}"]
},
{
  "name": "@aurelia-ux/form",
  "path": "../node_modules/@aurelia-ux/form/dist/amd",
  "main": "index",
  "resources": ["**/*.{css,html}"]
}

Using Components

The components are added as plugins to main.ts, like so:

aurelia.use
    .plugin(PLATFORM.moduleName('@aurelia-ux/core'))
    .plugin(PLATFORM.moduleName('@aurelia-ux/input'))
    .plugin(PLATFORM.moduleName('@aurelia-ux/button'))
    .plugin(PLATFORM.moduleName('@aurelia-ux/form'));

This makes them available everywhere, without needing further require tags.

Each component has a tag and properties which can be used in a template file. For instance the button is <ux-button> and can be bound to a view model variable using value.bind e.g. <ux-input value.bind="rows"></ux-input>. The elements appear unmodified in the output HTML and can be styled using CSS. Here is an example of using three components in a view model.
app.html:

<template>
  <require from="./app.css"></require>
  <ux-form>
    Rows:<ux-input value.bind="rows"></ux-input>
    Columns: <ux-input value.bind="cols"></ux-input>
    <ux-button type="raised" click.trigger="generate()">Generate</ux-button>
  </ux-form>
</template>

app.ts:

export class App {
    protected rows: number = 5; // arbitrary default
    protected cols: number = 5; // arbitrary default
    protected generate() { ... }
}

app.css (you may need to create this):

ux-form {
    padding: 2px;
    max-width: 300px;
    border: 1px solid transparent;
    border-color: var(--ux-design--primary-light)
}

CSS Variables

You may have noticed in the CSS above the use of a variable: var(--ux-design--primary-light). This is a mechanism for globally styling aurelia-ux, and is setup in the app constructor as follows:

import { autoinject } from 'aurelia-framework';
import { AureliaUX } from '@aurelia-ux/core';

@autoinject
export class App {
    protected rows: number = 5; // arbitrary default
    protected cols: number = 5; // arbitrary default

    // ux.design members: 
    //     appBackground, appForeground, controlBackground, controlForeground, 
    //     primary, primaryForeground, primaryLight, primaryLightForeground, primaryDark, primaryDarkForeground, 
    //     accent, accentForeground, accentLight, accentLightForeground, accentDark, accentDarkForeground
    constructor(private ux: AureliaUX) {
        ux.design.primary = '#DD0088';
        ux.design.accent = '#CC88FF';
        // unfortunately we don't seem to be able to change the tints by just setting the base color, so set them as well
        ux.design.primaryLight = '#DB9BBF';
        ux.design.accentLight = '#E7C9FF';
    }

    protected generate() { ... }
}

The name of the CSS variable is --ux-design--<property-name> where the property name is one of the ux.design members above. You can see them all in your browser’s CSS viewer:
CSS Design Variables
It is worth noting that CSS variables do not work in IE11 so if you expect to support that browser then aurelia-ux may not be suitable for you.

More

This is as far as I’ve gone with aurelia-ux so far, but there’s certainly more out there.

There are 11 components as it stands: button; checkbox; chip-input (a tag list); datepicker; form; input; input-info; list; radio; textarea; and switch. To find out what properties they expose download and run app-ux-showcase, or open your node_modules/@aurelia-ux/<name>/dist/amd/ux-<name>.d.ts and look at the public properties and methods.

There is also a themeing system which enables you to define your own component themes by extending implementations of the UxTheme class. Any properties in those classes can be used as CSS variables. To fully grasp this at present I’d suggest downloading the aurelia-ux source.

Aurelia return to URL after login

When a user accesses a URL without being authenticated we want to take them to the authentication page and then automatically redirect them to their requested URL after successful login. This is particularly important for following URLs from emails or other notifications, and for browser bookmarking.

Aurelia has a navigation pipeline which includes an authorization step and is executed for any navigation action. As part of an authorize step we can check whether the route is configured to require authentication and redirect the user to a login page (” in my case) if they are not.

class AuthorizeStep {
    protected run(navigationInstruction, next) {

        // determine if the route requires authorization
        // i.e. { route: '...', module: '...', setting: { auth: ... }}
        let authConfig = navigationInstruction.getAllInstructions().find(i => i.config.settings.auth);
        if (!authConfig)
            return next();

        let isLoggedIn = checkIfLoggedIn();
        if (!isLoggedIn)
            return next.cancel(new Redirect("")); // redirect to login page
        return next();
    }
}

To redirect after login we need to save the navigation instruction from the failed login and use it following the next successful login. To save it I introduced a NotAuthorizedRedirect class.

import { autoinject } from "aurelia-framework";
import { Router, Redirect, NavigationInstruction } from "aurelia-router";

// singleton by virtue of aurelia's default DI
@autoinject
export class NotAuthorizedRedirect {
    private navigationInstruction: NavigationInstruction;

    constructor(private router: Router) { }

    public notAuthorized(from: NavigationInstruction) {
        this.navigationInstruction = from;
        return new Redirect("");
    }

    public get redirecting() {
        return !!this.navigationInstruction;
    }

    public tryReturnTo(): string {
        if (!this.navigationInstruction)
            return "";

        let uri = this.navigationInstruction.router.generate(
            this.navigationInstruction.config.name,
            Object.assign(this.navigationInstruction.params, this.navigationInstruction.queryParams),
            { replace: true });

        this.navigationInstruction = null; // single use
        return uri;
    }
}

We use this class by first calling the notAuthorized method which saves the navigation instruction and returns a redirect to use. Once the user is known to be authorized we call tryReturnTo to generate a new url.

One issue I had was that some parameters come in via the query string. Thankfully the router generate method has a feature that any parameters given which are not in the route definition (i.e. the id in a route-name/:id) are applied as query string parameters, so tryReturnTo is able to copy the query string parameters (.queryParams) onto the other parameters to re-generate the query string.

We can now use these methods from the AuthorizeStep as follows:

class AuthorizeStep {
    constructor(private redirect: NotAuthorizedRedirect) { }

    protected run(navigationInstruction, next) {

        let authConfig = navigationInstruction.getAllInstructions().find(i => i.config.settings.auth);
        if (!authConfig)
            return next();

        let isLoggedIn = checkIfLoggedIn();
        if (!isLoggedIn)
            return next.cancel(this.redirect.notAuthorized(navigationInstruction));

        let redirectUri = this.redirect.tryReturnTo(); 
        if (redirectUri)
            return next.cancel(new Redirect(redirectUri)); // return to url from before login attempt

        return next();
    }
}

A minor limitation of this approach is that the original URL applied by the user disappears from the address bar when they are redirected to log in. To remedy this NotAuthorizedRedirect also has a little helper property, redirecting, which can be used to indicate to the user that they are being redirected, and could easily be extended to show the user the original url.

The workings of this were originally posted in an aurelia discourse thread.

Repeating tr in Aurelia

I stumbled into one of those subtle little gotchas today with Aurelia.

Browsers’ only allow tr tags inside a tbody, and td tags inside a tr. As such, if you want to repeat a table row in Aurelia then rather than add a custom tag, you need to indicate to Aurelia that your component element is replacing another element by setting the as-compose attribute.

<tr repeat.for="x of obj" as-element="my-element" x.bind="x"></tr>

Where I got caught was with the (my-)element template. I wanted table cells so I created the following:

<template>
  <require from="../../resources/elements/typeahead"></require>
  <td>${x.value}</td>
  ...
</template>

However this didn’t work – it effectively removed any td.
The problem was that during rendering Aurelia directly combines the template and parent, creating an invalid HTML structure like this:

<tr>
  <require from="../../resources/elements/typeahead"></require>
  <td>...

Fixing the problem then is very simple: just move the require inside the first td:

<template>
  <td>
    <require from="../../resources/elements/typeahead"></require>
    ${x.value}
  </td>
  ...
</template>

Aurelia After-Render

One problem I’ve run into a few times is needing to ensure an element is on the page before rendering it. This occurs with 3rd party libraries like Chart.js and with jQuery manipulations when combined with if.bind. One option is to use the attached() lifecycle method, however if the necessary data is fetched during created() is it often not ready by attached(), and therefore it is useful to have an ‘event’ that indicates that the said elements are now available to use.

The solution I used is a basic Custom Attribute. A custom attribute runs its own behaviour lifecycle. Because the attribute is on the element we are waiting for, when the attached() method fires we know the element is attached to the DOM, so we can call the function which is waiting for the element.

The full code for the attribute is below. The attribute will pass a reference to the element to the registered function.

import { bindable, autoinject, customAttribute } from 'aurelia-framework';

@autoinject
@customAttribute('after-render')
export class AfterRenderCustomAttribute {
    private value;

    constructor(private element: Element) { }

    attached() {
        this.value({ele: this.element});
    }
}

To use the attribute, ensure it is included in the template either via a <require> tag or by setting it globally using config.globalResources(). Because the target of the attribute is a method we use .call rather than .bind.

<canvas id="my-chart" after-render.call="drawChart(ele)"></canvas>

Typeahead and Aurelia

The pace of my blogging has dropped somewhat now I’m on a full-time engagement, and so far there haven’t been too many stories to share from the engagement. But the one that was perhaps most frustrating and in need of sharing was getting Twitter typeahead working with Aurelia.
I will prefix this with a disclaimer – these are steps as I best recollect them and I haven’t re-done them all as I’m not in the mood for breaking something that is (finally) working.

No longer maintained?

The first obstacle was finding the source code for typeahead. The sourced linked from the official looking page (linked earlier) points to this GitHub repository which, at the time of writing, had no commits for almost two years. However various google searches intimated more recent changes, and eventually it became clear that most people were referring to this which, in its own words, “is a maintained fork of twitter.com’s autocomplete search library, typeahead.js.”

Getting the library

The working typeahead repository can be obtained through npm:
npm install corejs-typeahead --save

This does not come with an index.d.ts so if you’re like me and using TypeScript then you’ll need a typings file. Despite the strong possibility it is for the wrong typeahead repository, the typings file returned by typings search typeahead seemed to work fine once installed by typings install dt~typeahead -GS

Typeahead is dependent on jQuery v2, so after fetching via npm, in my package.json I fixed the version as follows "jquery": "^2". Finding a typings for jQuery was straightforward but getting TypeScript to be happy with it was harder. In the end I found simply import 'jquery'; to be the most effective way of including it in a TypeScript file. Then there was the challenge with a collision on the $ variable (with angular-protractor) which I could only resolve by editing the typings file for jQuery:

declare module 'jquery' {
    export = jQuery;
}
declare var jQuery: JQueryStatic;
//declare var $: JQueryStatic;

This does mean jQuery must be referenced as jQuery(...) rather than $(...) in source code, but personally I don’t mind – as good as jQuery is, my sensitivities find there to be something a tiny bit wrong with a library deciding it can own a single letter global variable.

Aurelia Bundles

Typeahead isn’t really a module and thus the TypeScript declaration file doesn’t export one, but it does for Bloodhound which is hard to avoid if you want to do anything useful with typeahead. To import these they have to be included in the aurelia bundle, and best as I can tell, they have to be separate. So my aurelia.json looks like this:

"jquery",{
    "name": "Bloodhound",
    "path": "../node_modules/corejs-typeahead",
    "main": "dist/bloodhound",
    "deps": ["jquery"]
},{
    "name": "typeahead",
    "path": "../node_modules/corejs-typeahead",
    "main": "dist/typeahead.jquery",
    "deps": ["jquery", "Bloodhound"]
},

To actually use them, they can be fully imported as follows:

import 'jquery';
import 'Bloodhound';
import 'typeahead';

Bootstrap Integration

At this stage typeahead was working, but didn’t fit in with the UI which was otherwise styled via Bootstrap 3. The advice from the internet was to use Bootstrap-3-Typeahead which seemed fine initially, but I simply couldn’t get working with remote data sources. After a lot of stepping through library source code and periodic googling I discovered this bridge simply didn’t support remote (according to that ticket it may now have been fixed).

So instead of using the bridge, I got the styles from here and applied a couple of modifications:

.tt-hint {
    border: 0
}
/* Support inline use */
.form-inline span.twitter-typeahead {
  width: auto;
  vertical-align: middle;
}

The Code

The end result is an aurelia element that does typeahead. The code for that is in a gist here

The element requires a prepare-query function which sets the remote url and applies any necessary headers. This is an example of one:

prepareQueryFn(query, settings: JQueryAjaxSettings) {
    settings.url = this.apiUrl + 'search?query=' + encodeURIComponent(query);
    settings.headers = { 
        'Accept': 'application/json',
        'Authorization': this.getAuthToken()
    }
    return settings;
}

Aurelia, Bootstrap, and Sass

Today’s objective was to override Bootstrap variables to permit some theme customization. Overall the process had a few pitfalls and I found that many references to this combination of tools were now out of date, presumably the result of changes to Aurelia’s project structure since they were written.

Sass Preprocessor

I started with a default Aurelia setup. This supports CSS and doesn’t include a preprocessor for Sass. To help, I generated an Aurelia project using au new with the Sass preprocessor selected. This highlighted the first change needed which was to replace the cssProcessor in the aurelia.json file:

  "cssProcessor": {
    "id": "sass",
    "displayName": "Sass",
    "fileExtension": ".scss",
    "source": "src/**/*.scss"
  },

To pre-process Sass a pre-processor is required. I used gulp-sass, obtained via npm install gulp-sass --save-dev. save-dev is used because this is a build tool and not required at runtime. To introduce this into the build process, I changed tasks/process-css.ts

import * as gulp from 'gulp';
import * as changedInPlace from 'gulp-changed-in-place';
import * as sourcemaps from 'gulp-sourcemaps';
import * as sass from 'gulp-sass';
import * as project from '../aurelia.json';
import {build} from 'aurelia-cli';

export default function processCSS() {
  return gulp.src(project.cssProcessor.source)
    .pipe(changedInPlace({firstPass:true}))
    .pipe(sourcemaps.init())
    .pipe(sass().on('error', sass.logError))
    .pipe(build.bundle());
};

Then I added an .scss file and referenced it from the app.html with a .css extension: . At this point doing au run and opening the browser showed the scss styles applied.

Adding Bootstrap

Aurelia doesn’t create a physical CSS file, instead the build.bundle() call in tasks/process-css.ts adds it to the app-bundle.js. As the goal was to customize Bootstrap before the CSS is generated, then the generated CSS, and therefore Bootstrap, must be included in the app-bundle rather than the vendor-bundle (where the contact manager tutorial puts it). This meant removing jquery and Bootstrap from the vendor-bundle section of the aurelia.json and putting it in the app-bundle section as follows:

"bundles": [
      {
        "name": "app-bundle.js",
        "source": [
          "[**/*.js]",
          "**/*.{css,html}"
        ],
        "dependencies": [
          "jquery",
          "bootstrap-sass"
        ]
      },
      {
        "name": "vendor-bundle.js",
        ...

The json above is slightly out of order because it references bootstrap-sass, the Sass version of bootstrap. This was obtained using the command npm install bootstrap-sass --save. I also had to clear out the original Bootstrap by deleting it from the package.json and running npm prune, and then doing similar steps for Typings.
At this point the scss file was as follows:

$navbar-default-bg: #800;
@import '../node_modules/bootstrap-sass/assets/stylesheets/bootstrap';
div {
    border: 1px solid green;
}

Building this with au build resulted in fairly verbose and non-illuminating errors because in order to work Bootstrap depends on gulp-autoprefixer which needed to be added to the process-css.ts as follows:

import * as gulp from 'gulp';
import * as changedInPlace from 'gulp-changed-in-place';
import * as autoprefixer from 'gulp-autoprefixer';
import * as sourcemaps from 'gulp-sourcemaps';
import * as sass from 'gulp-sass';
import * as project from '../aurelia.json';
import {build} from 'aurelia-cli';

export default function processCSS() {
  return gulp.src(project.cssProcessor.source)
    .pipe(changedInPlace({firstPass:true}))
    .pipe(sourcemaps.init())
    .pipe(sass().on('error', sass.logError))
    .pipe(autoprefixer())
    .pipe(build.bundle());
};

With that, I successfully overwrote a Bootstrap variable.