Commit 0f179593 authored by Dave Methvin's avatar Dave Methvin

typescript-backbone: Add routing

Fixes #1487
parent 74ceed6e
...@@ -92,6 +92,17 @@ https://github.com/documentcloud/backbone/blob/master/examples/todos/index.html ...@@ -92,6 +92,17 @@ https://github.com/documentcloud/backbone/blob/master/examples/todos/index.html
<span class="word"><%= remaining == 1 ? 'item' : 'items' %></span> left <span class="word"><%= remaining == 1 ? 'item' : 'items' %></span> left
</span> </span>
<% } %> <% } %>
<ul class="filters">
<li>
<a class="selected" href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<% if (completed) { %> <% if (completed) { %>
<span class="todo-clear"> <span class="todo-clear">
<button class="clear-completed">Clear completed</button> <button class="clear-completed">Clear completed</button>
......
...@@ -39,11 +39,10 @@ DEALINGS IN THE SOFTWARE. ...@@ -39,11 +39,10 @@ DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------------- */ --------------------------------------------------------------------------------------- */
// Todos.js // Todos.js
// https://github.com/documentcloud/backbone/blob/master/examples/todos/todos.js // https://github.com/documentcloud/backbone/blob/master/examples/todos/todos.js
var __extends = this.__extends || function (d, b) { var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; } function __() { this.constructor = d; }
__.prototype = b.prototype; d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
d.prototype = new __();
}; };
// Todo Model // Todo Model
// ---------- // ----------
...@@ -112,6 +111,7 @@ var TodoList = (function (_super) { ...@@ -112,6 +111,7 @@ var TodoList = (function (_super) {
})(Backbone.Collection); })(Backbone.Collection);
// Create our global collection of **Todos**. // Create our global collection of **Todos**.
var Todos = new TodoList(); var Todos = new TodoList();
var taskFilter;
// Todo Item View // Todo Item View
// -------------- // --------------
// The DOM element for a todo item... // The DOM element for a todo item...
...@@ -132,13 +132,17 @@ var TodoView = (function (_super) { ...@@ -132,13 +132,17 @@ var TodoView = (function (_super) {
_super.call(this, options); _super.call(this, options);
// Cache the template function for a single item. // Cache the template function for a single item.
this.template = _.template($('#item-template').html()); this.template = _.template($('#item-template').html());
_.bindAll(this, 'render', 'close', 'remove'); _.bindAll(this, 'render', 'close', 'remove', 'toggleVisible');
this.model.bind('change', this.render); this.model.bind('change', this.render);
this.model.bind('destroy', this.remove); this.model.bind('destroy', this.remove);
this.model.bind('visible', this.toggleVisible);
} }
// Re-render the contents of the todo item. // Re-render the contents of the todo item.
TodoView.prototype.render = function () { TodoView.prototype.render = function () {
this.$el.html(this.template(this.model.toJSON())).toggleClass('completed', this.model.get('completed')); this.$el
.html(this.template(this.model.toJSON()))
.toggleClass('completed', this.model.get('completed'));
this.toggleVisible();
this.input = this.$('.todo-input'); this.input = this.$('.todo-input');
return this; return this;
}; };
...@@ -146,6 +150,12 @@ var TodoView = (function (_super) { ...@@ -146,6 +150,12 @@ var TodoView = (function (_super) {
TodoView.prototype.toggleDone = function () { TodoView.prototype.toggleDone = function () {
this.model.toggle(); this.model.toggle();
}; };
TodoView.prototype.toggleVisible = function () {
var completed = this.model.get('completed');
var hidden = (taskFilter === 'completed' && !completed) ||
(taskFilter === 'active' && completed);
this.$el.toggleClass('hidden', hidden);
};
// Switch this view into `'editing'` mode, displaying the input field. // Switch this view into `'editing'` mode, displaying the input field.
TodoView.prototype.edit = function () { TodoView.prototype.edit = function () {
this.$el.addClass('editing'); this.$el.addClass('editing');
...@@ -184,6 +194,25 @@ var TodoView = (function (_super) { ...@@ -184,6 +194,25 @@ var TodoView = (function (_super) {
TodoView.ESC_KEY = 27; TodoView.ESC_KEY = 27;
return TodoView; return TodoView;
})(Backbone.View); })(Backbone.View);
// Todo Router
// -----------
var TodoRouter = (function (_super) {
__extends(TodoRouter, _super);
function TodoRouter() {
_super.call(this);
this.routes = {
'*filter': 'setFilter'
};
this._bindRoutes();
}
TodoRouter.prototype.setFilter = function (param) {
if (param === void 0) { param = ''; }
// Trigger a collection filter event, causing hiding/unhiding
// of Todo view items
Todos.trigger('filter', param);
};
return TodoRouter;
})(Backbone.Router);
// The Application // The Application
// --------------- // ---------------
// Our overall **AppView** is the top-level piece of UI. // Our overall **AppView** is the top-level piece of UI.
...@@ -203,7 +232,7 @@ var AppView = (function (_super) { ...@@ -203,7 +232,7 @@ var AppView = (function (_super) {
// At initialization we bind to the relevant events on the `Todos` // At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by // collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*. // loading any preexisting todos that might be saved in *localStorage*.
_.bindAll(this, 'addOne', 'addAll', 'render', 'toggleAllComplete'); _.bindAll(this, 'addOne', 'addAll', 'render', 'toggleAllComplete', 'filter');
this.input = this.$('.new-todo'); this.input = this.$('.new-todo');
this.allCheckbox = this.$('.toggle-all')[0]; this.allCheckbox = this.$('.toggle-all')[0];
this.mainElement = this.$('.main')[0]; this.mainElement = this.$('.main')[0];
...@@ -212,7 +241,12 @@ var AppView = (function (_super) { ...@@ -212,7 +241,12 @@ var AppView = (function (_super) {
Todos.bind('add', this.addOne); Todos.bind('add', this.addOne);
Todos.bind('reset', this.addAll); Todos.bind('reset', this.addAll);
Todos.bind('all', this.render); Todos.bind('all', this.render);
Todos.bind('change:completed', this.filterOne);
Todos.bind('filter', this.filter);
Todos.fetch(); Todos.fetch();
// Initialize the router, showing the selected view
var todoRouter = new TodoRouter();
Backbone.history.start();
} }
// Re-rendering the App just means refreshing the statistics -- the rest // Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change. // of the app doesn't change.
...@@ -227,6 +261,10 @@ var AppView = (function (_super) { ...@@ -227,6 +261,10 @@ var AppView = (function (_super) {
completed: completed, completed: completed,
remaining: remaining remaining: remaining
})); }));
this.$('.filters li a')
.removeClass('selected')
.filter('[href="#/' + (taskFilter || '') + '"]')
.addClass('selected');
} }
else { else {
this.mainElement.style.display = 'none'; this.mainElement.style.display = 'none';
...@@ -244,6 +282,17 @@ var AppView = (function (_super) { ...@@ -244,6 +282,17 @@ var AppView = (function (_super) {
AppView.prototype.addAll = function () { AppView.prototype.addAll = function () {
Todos.each(this.addOne); Todos.each(this.addOne);
}; };
// Filter out completed/remaining tasks
AppView.prototype.filter = function (criteria) {
taskFilter = criteria;
this.filterAll();
};
AppView.prototype.filterOne = function (todo) {
todo.trigger('visible');
};
AppView.prototype.filterAll = function () {
Todos.each(this.filterOne);
};
// Generate the attributes for a new Todo item. // Generate the attributes for a new Todo item.
AppView.prototype.newAttributes = function () { AppView.prototype.newAttributes = function () {
return { return {
......
...@@ -46,6 +46,8 @@ DEALINGS IN THE SOFTWARE. ...@@ -46,6 +46,8 @@ DEALINGS IN THE SOFTWARE.
// to persist Backbone models within your browser. // to persist Backbone models within your browser.
declare var $: any; declare var $: any;
// TODO: Use DefinitelyTyped rather than ad-hoc definition here
declare module Backbone { declare module Backbone {
export class Model { export class Model {
constructor (attr? , opts? ); constructor (attr? , opts? );
...@@ -56,6 +58,7 @@ declare module Backbone { ...@@ -56,6 +58,7 @@ declare module Backbone {
destroy(): void; destroy(): void;
bind(ev: string, f: Function, ctx?: any): void; bind(ev: string, f: Function, ctx?: any): void;
toJSON(): any; toJSON(): any;
trigger(eventName: string, ...args: any[]): any;
} }
export class Collection { export class Collection {
constructor (models? , opts? ); constructor (models? , opts? );
...@@ -69,6 +72,7 @@ declare module Backbone { ...@@ -69,6 +72,7 @@ declare module Backbone {
last(n: number): any[]; last(n: number): any[];
filter(f: (elem: any) => any): Collection; filter(f: (elem: any) => any): Collection;
without(...values: any[]): Collection; without(...values: any[]): Collection;
trigger(eventName: string, ...args: any[]): any;
} }
export class View { export class View {
constructor (options? ); constructor (options? );
...@@ -85,6 +89,16 @@ declare module Backbone { ...@@ -85,6 +89,16 @@ declare module Backbone {
static extend: any; static extend: any;
} }
export class Router {
constructor (routes?: any );
routes: any;
}
export class History {
start(options?: any);
navigate(fragment: string, options: any);
pushState();
}
export var history: History;
} }
declare var _: any; declare var _: any;
declare var Store: any; declare var Store: any;
...@@ -161,7 +175,8 @@ class TodoList extends Backbone.Collection { ...@@ -161,7 +175,8 @@ class TodoList extends Backbone.Collection {
} }
// Create our global collection of **Todos**. // Create our global collection of **Todos**.
var Todos = new TodoList(); const Todos = new TodoList();
var taskFilter;
// Todo Item View // Todo Item View
// -------------- // --------------
...@@ -200,9 +215,10 @@ class TodoView extends Backbone.View { ...@@ -200,9 +215,10 @@ class TodoView extends Backbone.View {
// Cache the template function for a single item. // Cache the template function for a single item.
this.template = _.template($('#item-template').html()); this.template = _.template($('#item-template').html());
_.bindAll(this, 'render', 'close', 'remove'); _.bindAll(this, 'render', 'close', 'remove', 'toggleVisible');
this.model.bind('change', this.render); this.model.bind('change', this.render);
this.model.bind('destroy', this.remove); this.model.bind('destroy', this.remove);
this.model.bind('visible', this.toggleVisible);
} }
// Re-render the contents of the todo item. // Re-render the contents of the todo item.
...@@ -210,7 +226,7 @@ class TodoView extends Backbone.View { ...@@ -210,7 +226,7 @@ class TodoView extends Backbone.View {
this.$el this.$el
.html(this.template(this.model.toJSON())) .html(this.template(this.model.toJSON()))
.toggleClass('completed', this.model.get('completed')); .toggleClass('completed', this.model.get('completed'));
this.toggleVisible();
this.input = this.$('.todo-input'); this.input = this.$('.todo-input');
return this; return this;
} }
...@@ -220,6 +236,14 @@ class TodoView extends Backbone.View { ...@@ -220,6 +236,14 @@ class TodoView extends Backbone.View {
this.model.toggle(); this.model.toggle();
} }
toggleVisible() {
var completed = this.model.get('completed');
var hidden =
(taskFilter === 'completed' && !completed) ||
(taskFilter === 'active' && completed);
this.$el.toggleClass('hidden', hidden);
}
// Switch this view into `'editing'` mode, displaying the input field. // Switch this view into `'editing'` mode, displaying the input field.
edit() { edit() {
this.$el.addClass('editing'); this.$el.addClass('editing');
...@@ -261,6 +285,28 @@ class TodoView extends Backbone.View { ...@@ -261,6 +285,28 @@ class TodoView extends Backbone.View {
} }
// Todo Router
// -----------
class TodoRouter extends Backbone.Router {
routes = {
'*filter': 'setFilter'
};
constructor() {
super();
(<any>this)._bindRoutes();
}
setFilter(param: string = '') {
// Trigger a collection filter event, causing hiding/unhiding
// of Todo view items
Todos.trigger('filter', param);
}
}
// The Application // The Application
// --------------- // ---------------
...@@ -289,7 +335,7 @@ class AppView extends Backbone.View { ...@@ -289,7 +335,7 @@ class AppView extends Backbone.View {
// At initialization we bind to the relevant events on the `Todos` // At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by // collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*. // loading any preexisting todos that might be saved in *localStorage*.
_.bindAll(this, 'addOne', 'addAll', 'render', 'toggleAllComplete'); _.bindAll(this, 'addOne', 'addAll', 'render', 'toggleAllComplete', 'filter');
this.input = this.$('.new-todo'); this.input = this.$('.new-todo');
this.allCheckbox = this.$('.toggle-all')[0]; this.allCheckbox = this.$('.toggle-all')[0];
...@@ -300,8 +346,13 @@ class AppView extends Backbone.View { ...@@ -300,8 +346,13 @@ class AppView extends Backbone.View {
Todos.bind('add', this.addOne); Todos.bind('add', this.addOne);
Todos.bind('reset', this.addAll); Todos.bind('reset', this.addAll);
Todos.bind('all', this.render); Todos.bind('all', this.render);
Todos.bind('change:completed', this.filterOne);
Todos.bind('filter', this.filter);
Todos.fetch(); Todos.fetch();
// Initialize the router, showing the selected view
const todoRouter = new TodoRouter();
Backbone.history.start();
} }
// Re-rendering the App just means refreshing the statistics -- the rest // Re-rendering the App just means refreshing the statistics -- the rest
...@@ -319,6 +370,12 @@ class AppView extends Backbone.View { ...@@ -319,6 +370,12 @@ class AppView extends Backbone.View {
completed: completed, completed: completed,
remaining: remaining remaining: remaining
})); }));
this.$('.filters li a')
.removeClass('selected')
.filter('[href="#/' + (taskFilter || '') + '"]')
.addClass('selected');
} else { } else {
this.mainElement.style.display = 'none'; this.mainElement.style.display = 'none';
this.footerElement.style.display = 'none'; this.footerElement.style.display = 'none';
...@@ -329,7 +386,7 @@ class AppView extends Backbone.View { ...@@ -329,7 +386,7 @@ class AppView extends Backbone.View {
// Add a single todo item to the list by creating a view for it, and // Add a single todo item to the list by creating a view for it, and
// appending its element to the `<ul>`. // appending its element to the `<ul>`.
addOne(todo) { addOne(todo: Todo) {
var view = new TodoView({ model: todo }); var view = new TodoView({ model: todo });
this.$('.todo-list').append(view.render().el); this.$('.todo-list').append(view.render().el);
} }
...@@ -339,6 +396,20 @@ class AppView extends Backbone.View { ...@@ -339,6 +396,20 @@ class AppView extends Backbone.View {
Todos.each(this.addOne); Todos.each(this.addOne);
} }
// Filter out completed/remaining tasks
filter(criteria: string) {
taskFilter = criteria;
this.filterAll();
}
filterOne(todo: Todo) {
todo.trigger('visible');
}
filterAll() {
Todos.each(this.filterOne);
}
// Generate the attributes for a new Todo item. // Generate the attributes for a new Todo item.
newAttributes() { newAttributes() {
return { return {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment