TasteJS blog
Published in

TasteJS blog

Rewriting A WebApp With ECMAScript 6

Introduction

Features covered

Source & Demos

Begin your ES6 adventure here

Index (index.html)

Application entry-point (app.js)

Imports

import {AppView, Filters} from './todo-app';

Document ready

Arrow Functions (Statements)

$(() => { // Finally, we kick things off by creating the App. new AppView(); new Filters(); Backbone.history.start(); });

TodoApp Module (todo-app.js)

Destructuring Assignments

const { Model, View, Collection, Router, LocalStorage } = Backbone;
var { Model, View, Collection, Router, LocalStorage } = Backbone; // Note, if Backbone was written with ES6 you // would use an import for this. var ENTER_KEY = 13; // const var TodoFilter = ''; // let

Todo Model class

Classes

class Todo extends Model { // Note the omission of the 'function' keyword // Method declaractions inside ES6 classes don't // use it // Define some default attributes for the todo. defaults() { return { title: '', completed: false }; } // Toggle the `completed` state of this todo item. toggle() { this.save({ completed: !this.get('completed') }); } }

TodoList Collection class

class TodoList extends Collection {

Constructors and Super Constructors

constructor(options) { super(options); // Hold a reference to this collection's model. this.model = Todo; // Save all of the todo items under the 'todos' namespace. this.localStorage = new LocalStorage('todos-traceur-backbone'); }

Arrow Functions (Expressions)

completed() { return this.filter(todo => todo.get('completed')); }
remaining() { // The ES6 spread operator reduces runtime boilerplate  // code by allowing an expression to be expanded where  // multiple arguments or elements are normally expected.  // It can appear in function calls or array literals. // The three dot syntax below is to indicate a variable // number of arguments and helps us avoid hacky use of // `apply` for spreading. // Compare the old way... return this.without.apply(this, this.completed()); // ...with the new, significantly shorter way... return this.without(...this.completed()); // This doesn't require repeating the object on which  // the method is called - (`this` in our case). }
nextOrder() { if (!this.length) { return 1; } return this.last().get('order') + 1; }
comparator(todo) { return todo.get('order'); } }
var Todos = new TodoList();

Todo Item View class

class TodoView extends View { constructor(options) { super(options); // The DOM element for a todo item // is a list tag. this.tagName = 'li';
// Cache the template function for a single item. this.template = _.template($('#item-template').html()); this.input = ''; // Define the DOM events specific to an item. this.events = { 'click .toggle': 'toggleCompleted', 'dblclick label': 'edit', 'click .destroy': 'clear', 'keypress .edit': 'updateOnEnter', 'blur .edit': 'close' }; this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'destroy', this.remove); this.listenTo(this.model, 'visible', this.toggleVisible); }
render() { this.$el.html(this.template(this.model.toJSON())); this.$el.toggleClass('completed', this.model.get('completed')); this.toggleVisible(); this.input = this.$('.edit'); return this; } toggleVisible() { this.$el.toggleClass('hidden', this.isHidden); }

Accessor Properties

get isHidden() { var isCompleted = this.model.get('completed'); return (hidden cases only (!isCompleted && TodoFilter === 'completed') || (isCompleted && TodoFilter === 'active') ); }
toggleCompleted() { this.model.toggle(); }
edit() { var value = this.input.val(); this.$el.addClass('editing'); this.input.val(value).focus(); }
close() { var title = this.input.val(); if (title) { // Note that here we use the new  // object literal property value  // shorthand this.model.save({ title }); } else { this.clear(); } this.$el.removeClass('editing'); }
updateOnEnter(e) { if (e.which === ENTER_KEY) { this.close(); } }
clear() { this.model.destroy(); } }

The Application class

export class AppView extends View { constructor() {
this.setElement($('#todoapp'), true); this.statsTemplate = _.template($('#stats-template').html()),
this.events = { 'keypress #new-todo': 'createOnEnter', 'click #clear-completed': 'clearCompleted', 'click #toggle-all': 'toggleAllComplete' };
this.allCheckbox = this.$('#toggle-all')[0]; this.$input = this.$('#new-todo'); this.$footer = this.$('#footer'); this.$main = this.$('#main'); this.listenTo(Todos, 'add', this.addOne); this.listenTo(Todos, 'reset', this.addAll); this.listenTo(Todos, 'change:completed', this.filterOne); this.listenTo(Todos, 'filter', this.filterAll); this.listenTo(Todos, 'all', this.render); Todos.fetch(); super(); }
render() { var completed = Todos.completed().length; var remaining = Todos.remaining().length; if (Todos.length) { this.$main.show(); this.$footer.show(); this.$footer.html( this.statsTemplate({ completed, remaining }) ); this.$('#filters li a') .removeClass('selected') .filter(`[href="#/${TodoFilter || ''}"]`) .addClass('selected'); } else { this.$main.hide(); this.$footer.hide(); } this.allCheckbox.checked = !remaining; }
addOne(model) { var view = new TodoView({ model }); $('#todo-list').append(view.render().el); }
addAll() { this.$('#todo-list').html(''); Todos.each(this.addOne, this); } filterOne(todo) { todo.trigger('visible'); } filterAll() { Todos.each(this.filterOne, this); }
newAttributes() { return { title: this.$input.val().trim(), order: Todos.nextOrder(), completed: false }; }
createOnEnter(e) { if (e.which !== ENTER_KEY || !this.$input.val().trim()) { return; } Todos.create(this.newAttributes()); this.$input.val(''); }
clearCompleted() { _.invoke(Todos.completed(), 'destroy'); } toggleAllComplete() { var completed = this.allCheckbox.checked; Todos.each(todo => todo.save({ completed })); } }

The Filters Router class

export class Filters extends Router { constructor() { this.routes = { '*filter': 'filter' } this._bindRoutes(); }

Default Parameters

function hello(firstName, lastName) { firstName = firstName || 'Joe'; lastName = lastName || 'Schmo'; return 'Hello, ' + firstName + ' ' + lastName; }
function hello(firstName = 'Joe', lastName = 'Schmo') { return `Hello, ${firstName} ${lastName}`; }
filter(param = '') { // Set the current filter to be used. TodoFilter = param; // Trigger a collection filter event,  // causing hiding/unhiding of Todo view  // items. Todos.trigger('filter'); } }

Web Components

Tracking ECMAScript 6 Support

ES6 Tools

Wrapping up

--

--

A better JavaScript learning experience. Keeper of TodoMVC & @PropertyCross. Eater of popsicles. http://tastejs.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
TasteJS

A better JavaScript learning experience. Keeper of TodoMVC & @PropertyCross. Eater of popsicles. http://tastejs.com