You are on page 1of 32

Writing HTML5 Web Apps

Google App Engine Backbone.js Require.js jQuery

Ron Reiter 2012

Agenda
Why do you need to learn how to program HTML5 web apps? A walkthrough over the To-Do list anatomy
https://github.com/ronreiter/webapp-boilerplate

Why?
Web Apps = Software as a Service
Cross OS/platform/browser Cuts costs on deployment and maintenance Scales easily

Google App Engine, Amazon, Heroku, etc.

Mobile Apps
Cross device development cuts development costs WebView PhoneGap

Brief Introduction
Google App Engine - Solid, scalable server framework
Platform as a Service

Backbone.js - Proven MVC framework


LinkedIn mobile, Foursquare, Do.com, Groupon, Posterous, Basecamp mobile, Kicksend, etc...

Require.js - Modular JavaScript Loader


Module dependency management

JavaScript minification & bundling

jQuery - DOM Manipulation Framework


Creating dynamic content and replacing Flash

The To-Do List

Web App Architecture


Front End Backbone.js

REST API

Back End Google App Engine

Back-End

Dataset
We want to create a Todo list item table. Start by adding a Google App Engine model

# the Todo model. class Todo(db.Model): content = db.StringProperty()

done = db.BooleanProperty()
order = db.IntegerProperty()

Request Handler
Serves all web requests We implement a main handler and REST API def main():

application = webapp.WSGIApplication([
# index.html ('/', MainHandler), # REST interface ('/todos', TodoListHandler), ('/todos/(\d+)', TodoItemHandler), ], debug=True) util.run_wsgi_app(application)

Main Request Handler


When we access http://www.todolist.com, the app is downloaded to our computer and starts to run. class MainHandler(webapp.RequestHandler):

def get(self):
self.response.out.write( template.render("index.html", {}))

REST API Standard


REST API is used to let clients control datasets, using four CRUD operations:
Create a new item using HTTP POST requests Read a list of items using HTTP GET requests

Update a single item using HTTP PUT requests


Delete a single item using HTTP DELETE requests

Similar to SQL, but this is a customized interface and not a way to access a database REST API uses XML or JSON serialization (Javascript Object Notation) to encode and decode objects Well use JSON

REST API GET (get all tasks)


class TodoListHandler (webapp.RequestHandler): def get(self): # serialize all Todos, # include the ID in the response todos = []

for todo in Todo.all(): todos.append({ "id" : todo.key().id(), "content" : todo.content, "done" : todo.done,

"order" : todo.order,
})

# send them to the client as JSON self.response.out.write(simplejson.dumps(todos))

REST API POST (add a new task)


class TodoListHandler (webapp.RequestHandler): def post(self): data = simplejson.loads(self.request.body) # load JSON data of the object

todo = Todo( content = data["content"], done = data["done"], order = data["order"], ).put() # create the todo item

# send it back, and include the new ID.

self.response.out.write(simplejson.dumps({
"id" : todo.id(), "content" : data["content"], "done" : data["done"], "order" : data["order"], }))

REST API PUT (update a task)


class TodoItemHandler (webapp.RequestHandler): def put(self, id): data = simplejson.loads(self.request.body) # load the updated model

todo = Todo.get_by_id(int(id)) # get it model using the ID from the request path todo.content = data["content"] todo.done = data["done"] todo.order = data["order"] todo.put() # update all fields and save to the DB

# send it back using the updated values

self.response.out.write(simplejson.dumps({
"id" : id, "content" : todo.content, "done" : todo.done, "order" : todo.order, }))

REST API DELETE (delete a task)


class TodoItemHandler (webapp.RequestHandler): def delete(self, id):

# find the requested model and delete it. todo = Todo.get_by_id(int(id)) todo.delete()

Front-End

Backbone.js Architecture MVC


Server Model
Backbone REST Sync Database

Backbone.Model

Model

HTML + CSS
DOM Manipulation With jQuery and templating

View

Model Events

Backbone.View

Controller

View Events

Backbone.js Architecture REST Sync

Collection Operations GET /tasks POST /tasks

Collection
View View View View Model Model Model Model

Model Operations PUT /tasks/38 DELETE /tasks/38 PUT /tasks/39 DELETE /tasks/39 PUT /tasks/40 DELETE /tasks/40 PUT /tasks/41 DELETE /tasks/41

Web App Directory Structure


index.html Main entry point
css todos.css Defines the style js libs require.js, jQuery, Backbone, Underscore models todo.js The todo item model collections todos.js The todo item collection views todos.js The todo item view App.js The app view templates stats.html todo.html The todo item template main.js Require.js entry point

index.html
Loads the stylesheet <link rel="stylesheet" href="css/todos.css/> Loads the main.js script <script data-main="js/main" src="js/libs/require/require.js"> </script>

main.js
Configures paths and known libraries text is used for require.js text loading (for templates) require.config({ paths: { jquery: 'libs/jquery/jquery-min', underscore: 'libs/underscore/underscore-min',

backbone: 'libs/backbone/backbone-optamd3-min',
text: 'libs/require/text' } });

main.js cont.
Load the app view (views/app.js) Notice there is no need to add the JS extension Require.js supplies us with a function to run Javascript code only after certain modules have been loaded. This allows us to create a dependency model, and a new way to write Javascript modules.

require(['views/app'], function(AppView){ var app_view = new AppView; });

views/app.js The AppView


Backbone's "View" is actually a "View Controller". The view itself is the template combined with CSS. Creating a new view either means
creating a new DOM element using JavaScript/jQuery or templating Using an existing one in the DOM

Since there is only one app, well use an existing element for the AppView. However, task views are more dynamic, and they will create new DOM elements.

views/app.js The AppView (cont.)


The define function allows us to depend on libraries, template files and other modules we wrote. Every dependency in the list corresponds to an argument of the function to execute once all modules are loaded. define([ 'jquery, 'underscore', 'backbone', 'collections/todos, 'views/todos', 'text!templates/stats.html' ], function($, _, Backbone, Todos, TodoView, statsTemplate) { var AppView = Backbone.View.extend({ ...

views/app.js The AppView (cont.)


The "View" captures and delegates events on DOM elements

events: { "keypress #new-todo": "createOnEnter", "click .todo-clear a": "clearCompleted },

views/app.js The AppView (cont.)


We add some handlers on the Todos collection to get notified when it's updated. Then, we fetch the Todos collection from the server. Once the data is loaded, addAll will be executed. initialize: function() { Todos.bind('add', this.addOne);

Todos.bind('reset', this.addAll);
Todos.fetch(); },

collections/todos.js Todo Collection


Backbone Collections are model arrays which synchronize with the server. The AppView listens for changes on this collection, and can add new models to it.

define([ 'underscore', 'backbone', 'models/todo ], function(_, Backbone, Todo){ var TodosCollection = Backbone.Collection.extend({ model: Todo, url: '/todos', done: function() {

return this.filter(function(todo){
return todo.get('done'); }); } });

models/todo.js Todo Model


Holds the Todo item data Defines the default values and methods related to the data The "define" function needs to return the model class. When "model/todo" will be required, it will be received. define(['underscore', 'backbone'], function(_, Backbone) { var TodoModel = Backbone.Model.extend({ defaults: { content: "empty todo..., done: false, order: 0 }

});
return TodoModel; });

views/todos.js Todo View


Bind the view to the model Render function uses the model data to render the template
define(['jquery', 'underscore', 'backbone, 'models/todo', 'text!templates/todos.html' ], function($, _, Backbone, Todo, todosTemplate){ var TodoView = Backbone.View.extend({ model: Todo, template: _.template(todosTemplate), initialize: function() {

this.model.bind('change', this.render);
this.model.bind('destroy', this.remove); }, render: function() { $(this.el).html(this.template(this.model.toJSON()));
...

views/todos.js Todo View (cont.)


Manipulate the view's model according to triggered events events: { "click .check" : "toggleDone", ... }

// Toggle the "done" state of the model.


toggleDone: function() { this.model.save({done : !this.model.get("done")}); },

templates/todos.html
Template files are used to build the views either: Once (and then update using jQuery) On every update (if you're lazy) Use <%- ... -> for escaping HTML
<div class="todo <%= done ? 'done' : '' %>"> <div class="display"> <input class="check" type="checkbox" <%= done ? 'checked="checked"' : '' %> /> <div class="todo-content"><%- content %></div> <span class="todo-destroy"></span> </div> <div class="edit"> <input class="todo-input" type="text" value="<%- content %>" /> </div> </div>

Questions?

You might also like