Skip to content

Frameworks, Au2 Intro

Variety of frameworks

So .. many.. choices ...

React, Angular, Vue, Svelte, Ember, Backbone, Meteor, Aurelia, Polymer, Mihtril, Express, Sails, Koa, LoopBack, Hapy, OpenUI5, Feathers, Dojo, Web Components

Usage of frameworks/libraries

Most of the JS frontend libraries/frameworks are only concerned with UI. Several libraries are needed to get everything.

Writing bigger/huge apps in JS gets messy really quickly.

  • Routing
  • State management
  • Dependency injection
  • Data binding
  • Signaling
  • Communication between modules
  • Localization
  • Animations
  • Mixing html and js / templating
  • Typescript

During this course

Initial plan

  • Aurelia – Easiest full framework, good to start with. Standards based, convention over configuration.
  • React – View library from FB. State management gets complex.
  • Vue.js – View library, inspired by React. Simple, lightweight.

Aurelia 2 (Au)

  • Install node and npm.
  • Install into VS Code
    • aurelia-vscode-extension

Initial project

  1. npx makes aurelia
  2. Give name for your app
  3. Choose Default Typescript Aurelia App, install dependencies using npm

    (Or Custom app….)

  4. Cd into your app. Run >code . >npm run start

Overview - Main layout

Web main layout is in index.html. Do not touch!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Aurelia</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <base href="/">
</head>
<body>
    <my-app></my-app>
</body>
</html>

Overview - Structure

  • All the app code is in \src
    • main.ts – app entry point (matches with aurelia-app=“main” in index.ejs)
    • my-app.ts – main page. my-app.html – view template for app.ts
1
2
3
4
5
6
import Aurelia from 'aurelia';
import { MyApp } from './my-app';

Aurelia
    .app(MyApp)
    .start();
1
2
3
4
5
// contents of my-app.html

<div class="message">
    ${message}
</div>
1
2
3
4
5
// contents of my-app.ts

export class MyApp {
    public message: string = 'Hello World!';
}

Uses:

  • ES2015+
  • Typescript

Model-View-View-Model (MVVM)

Aurelia follows a MVVM software architectural pattern (Model-View-View-Model). The intent of this is to provide a clean "separation of concerns" between the user interface controls and their logic. It also helps to achieve maintainability, extensibility and testability of the app's code.

The MVVM pattern has been derived from the MVC (Model-View-Controller) model. The main difference between them is that all interactions in MVVM between Model and ViewModel go through binding system, which set loosely coupled relations between View’s elements and Model’s properties. By comparison, MVC goes through method calls.

Aurelia - A simple app

  • List manager (shoping/todo list?)

    • Basically CRUD over list
    • Bootstrap for css
  • Start by designing your UI in pure html

    • Install packages for css and html
      > npm i jquery popper.js bootstrap font-awesome
      > npm i --save-dev @types/jquery @types/bootstrap

CSS

Include design js and css in main.ts (global import).

1
2
3
4
5
6
import 'jquery';
import 'popper.js';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'font-awesome/css/font-awesome.min.css';
import '../static/site.css';

Create site.css in /static.

1
2
3
body {
    padding-top: 5rem;
}

bootstrap template

Modify app.html to include basic bootstrap template.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<template>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
        <a class="navbar-brand" href="#">Aurelia</a>
        <button class="navbar-toggler" type="button" 
        data-toggle="collapse" data-target="#navbarsExampleDefault"
        aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarsExampleDefault">
        <ul class="navbar-nav mr-auto">
            <li class="nav-item active">
                <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
            </li>
        </ul>
        </div>
    </nav>
    <main role="main" class="container">
        <h1>${message}</h1>
    </main>
</template>

Domain, Data

  • Describe your domain objects via interfaces
  • Simple state as array
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { ITodo } from './domain/ITodo';

export class App {
    public todos: ITodo[] = [];
    constructor() {
        this.todos.push(
            {
                description: "World domination", 
                done: false
            },
            {
                description: "More homeworks",
                done: true
            }
        );
    }
}
1
2
3
4
export interface ITodo {
    description: string;
    done: boolean;
}

Data binding to/from View

  • Simple string interpolation
  • Since this happens in runtime - public/private do not apply actually
  • Binding to html/svg attributes - attribute.<command>="expression”
  • command - one of Aurelia's attribute binding commands:
    • one-time - flows data one direction, from the view-model to the view, once.
    • to-view / one-way - flows data one direction, from the view-model to the view.
    • from-view - flows data one direction, from the view to the view-model.
    • two-way - flows data both ways, from view-model to view and from view to view-model.
    • bind - automatically chooses the binding mode. Uses two-way binding for form controls and to-view binding for almost everything else.

Binding DOM events

  • on<event>.<command>="expression"
  • event - the name of a DOM event, without the "on" prefix. E.g. click
  • Command:
    • trigger - attaches an event handler directly to the element. When the event fires, the expression will be invoked.
    • delegate - attaches a single event handler to the document (or nearest shadow DOM boundary) which handles all events of the specified type in bubbling phase, properly dispatching them back to their original targets for invocation of the associated expression.
  • expression - a JavaScript expression. Use the special $event property to access the DOM event in your binding expression.

binding

1
2
3
4
5
6
7
<form class="add-items d-flex" name="todo-input" submit.trigger="submitForm($event)">

<input value.bind="todoDescription" type="text" class="form-control todo-list-input" placeholder.one-time="placeholder">

<button class="add btn btn-primary font-weight-bold todo-list-add-btn">Add</button>

</form>
1
2
3
4
5
6
7
public placeholder: string = "What do you need to do today?"
public todoDescription: string = "";

submitForm(event: Event){
    console.log("submit", this.todoDescription, event);
    return false; // prevent default
}

binding to arrays

1
2
3
4
<li repeat.for="todo of todos">
    <input class="checkbox" type="checkbox" checked.bind="todo.done">
    ${todo.description}
</li>
  • repeat.for=”item of items”
  • Contextual Properties
    • $parent - Explicitly accesses the outer scope from within a compose or repeat template. You may need this when a property on the current scope masks a property on the outer scope. Chainable - eg $parent.$parent.foo is supported.
    • $index - In a repeat template, the index of the item in the collection.
    • $first- In a repeat template, is true if the item is the first item in the array.
    • $last - In a repeat template, is true if the item is the last item in the array.
    • $even - In a repeat template, is true if the item has an even numbered index.
    • $odd - In a repeat template, is true if the item has an odd numbered index.

Contextual properties

  • The binding system makes several properties available for binding in your templates, depending on the context.
    • $this - The binding context (the view-model).
    • $event - The DOM Event in delegate or trigger bindings.
    • $parent - Explicitly accesses the outer scope from within a compose or repeat template. You may need this when a property on the current scope masks a property on the outer scope. Chainable- eg $parent.$parent.foo is supported.

binding html class/style

You can bind an element's class attribute using string interpolation or with .bind

1
<li repeat.for="todo of todos" class.bind="todo.done ? 'completed' : ''">
  • Style
    • bind string or object to elements style attribute.
  • When you want to use string interpolation ${} – use custom css attribute.
1
2
3
4
5
this.styleString = 'color: red; background-color: blue';
this.styleObject = {
    color: 'red',
    'background-color': 'blue'
};

binding checkboxes

Bind a boolean property to an input element's checked attribute using checked.bind="myBooleanProperty".

updating state

Do not use direct array manipulation – this is not observable Use array methdos – splice, pop, push, etc…

Todo app - view

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<form class="add-items d-flex" name="todo-input" submit.trigger="submitForm($event)">
    <input value.bind="todoDescription" type="text" class="form-control todo-list-input"
    placeholder.one-time="placeholder">
    <button class="add btn btn-primary font-weight-bold todo-list-add-btn">Add</button>
</form>
<div class="list-wrapper">
    <ul class="d-flex flex-column todo-list">
        <li repeat.for="todo of todos" class.bind="todo.done ? 'completed' : ''">
        <div class="form-check">
            <label class="form-check-label">
                <input class="checkbox" type="checkbox" checked.bind="todo.done">
        ${todo.description} <i class="input-helper"></i>
            </label>
        </div>
            <i class="remove mdi mdi-close-circle-outline" click.delegate="removeTodo($index)"></i>
        </li>
    </ul>
</div>

Todo app - code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { ITodo } from './domain/ITodo';

export class App {
    public todos: ITodo[] = [];
    public placeholder: string = "What do you need to do today?"
    public todoDescription: string = "";

    constructor() {}

    submitForm(event: Event) {
        if (this.todoDescription.length > 0) {
            this.todos.push({ description: this.todoDescription, done: false });
            this.todoDescription = '';
        }
        event.preventDefault();
    }

    removeTodo(index: string) {
        this.todos.splice(Number(index), 1);
    }
}