Rp 6 session 2 naresh bhatia

Preview:

Citation preview

Architecting Web Applications Build Modular Web Applications Using Backbone.js and RequireJS

Naresh Bhatia

CTO, Sapient Global Markets Co-lead Visualization Practice

Experience Trading and Risk Management applications using JavaScript, Java and .NET

Founder Archfirst (http://archfirst.org) A place for software developers to learn and compare technologies through real world examples

A case study in visualization

A technologist’s side-project that was functional, but lacked finesse.

Before

In Between

After

What does it take to build such applications?

JavaScript Space used to be The Wild West

Sliding banners

5000 lines of procedural code in a single file

Complex DOM spaghetti

The landscape is changing now

Recent Advances in JavaScript

• AJAX enables changing of content without refreshing the entire page

• Browsers implement better and faster JavaScript engines

• Rise of smart web “applications” (vs. static web sites) – Gmail

– Facebook

– Twitter

• People start talking about “architecture” of their JavaScript applications – Object-orientation

– Design patterns

– MVC

– In-application messaging

– Modularity

JavaScript is a respectable platform today

Serious JavaScript developers do worry

about architecture these days!

Model-View-Controller (MVC) A pattern that encourages separation of concerns and code reuse

MVC Philosophy

Separate application state from its presentation

Separate models from their views

Get the truth out of the DOM

MVC Philosophy – Get the truth out of the DOM

Doesn’t matter what flavor of MVC you are using

MVC, MVP, MVVM, MV*, MVWhatever

Models

Views

Introducing Backbone – A Lightweight MV* Framework

• Organize your interface into logical views backed by models – Each view responsible for one DOM element

– Each view can be updated independently when the model changes

– Reduces DOM traversal, increasing performance

• See here for an example (slide 8)

• No need to dig into a JSON object, look up an element in the DOM, and update the HTML by hand – Bind your view's render function to the model's "change" event

– Now every view that displays this model is updated immediately on a change

Backbone Models

• Models are Backbone objects that contain “attributes”

• Whenever an attribute’s value changes, the model triggers a change event

// Define a model var Tile = Backbone.Model.extend({ }); // Create an instance of the model var tile = new Tile( ); // Set an attribute to fire the change event tile.set({color: 0xF89F1B});

“change” event Tile

color: F89F1B

Updating views from change events

tile

tileRectView

color: F89F1B

el: model:

// 1. Create an instance of the model var tile = new Tile( ); // 2. Create a view, attach it to the model & DOM var tileRectView = new TileRectView ({el: '#tile‘, model: tile}); // 3. (constructor) Bind view to model change event this.model.on('change', this.render); // 4. Change a model attribute tile.set({color: 0xF89F1B}); // 5. Model fires a ‘change’ event // 6. View catches the event & calls render( ) this.$el.css( 'background-color', '#' + color2hex(this.model.get('color')));

1

2

render( )

4

‘change’ event

6

DOM

<div id="tile" ></div>

5

3

Collections

brokerageAccounts

accountTableView

el: collection:

this.$el.empty(); this.collection.each(function(account, i) { var view = new AccountView({model: account}); this.$el.append(view.render().el); }, this);

DOM

<table id="accounts_table"> <thead>...</thead> <tbody></tbody> </table>

initialize( ) render( )

this.collection.bind('reset', this.render);

brokerageAccount

brokerageAccount

brokerageAccount

Composing an interface from multiple views – View Hierarchy

AccountsPage

AccountsTab

UserPageHeaderWidget

AccountTableWidget AccountChartWidget

AccountView

FooterWidget

AccountView

AccountView

AccountView

Inserting pages and widgets dynamically

index.html

<!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <!-- Pages go here --> </div> </body> </html>

A typical page

AccountsPage.js

BaseView.extend({ postRender: function() { this.addChildren([ { viewClass: UserPageHeaderWidget, parentElement: this.$el }, { viewClass: AccountsTab, parentElement: this.$el }, { viewClass: FooterWidget, parentElement: this.$el } ]); } }

A typical widget

UserPageHeaderWidget.js

BaseView.extend({ tagName: 'header', className: 'user-page-header', template: { name: 'UserPageHeaderTemplate', source: UserPageHeaderTemplate } });

UserPageHeaderTemplate.html

<div class="user-info"> <span>{{firstName}} {{lastName}}</span> <img src="img/person-icon-small.png" alt="seperator" /> <a class="js-signOut" href="#">Sign Out</a> </div> <h1 class="ir">Bullsfirst</h1>

Classic Server-Side Layered Architecture

User Interface Layer

Accepts user commands and presents information back to the user

Application Layer

Manages transactions, translates DTOs, coordinates application activities, creates and accesses domain objects

Domain Layer

Contains the state and behavior of the domain

Infrastructure Layer

Supports all other layers, includes repositories, adapters, frameworks etc.

Bullsfirst Client-Side Layered Architecture F

ram

ew

ork

P

rese

nta

tio

n L

aye

r D

om

ain

In-Application Messaging

• Interactions between table and chart

• If theses widgets interacted directly, the coupling would be too tight

• Use a message bus (a.k.a. pub/sub) to decouple the components

Account : mouseover

JavaScript Modularity A pattern that supports large scale application development

How much of your application can you hold in your head at once?

Bullsfirst is about 2000 lines of JS

Have you ever been in Dependency Hell?

<script src="js/form2js.js"></script>

<script src="js/toObject.js"></script>

<script src="js/base64_encode.js"></script>

<script src="js/utf8_encode.js"></script>

<script src="js/format.js"></script>

<script src="js/treeTable.js"></script>

<script src="js/jalerts.js"></script>

AMD and RequireJS to the rescue

AMD: Asynchronous Module Definition (an API for defining and loading JS modules)

RequireJS: A popular implementation of AMD

Modules allow you to break large applications into bite-size chunks

Code reuse & separation of concerns

Complements the MVC pattern

Structure of a Modular App

src |-- js | |-- app | | |-- domain | | | |-- Repository.js | | | |-- BrokerageAccount.js | | | `-- ... | | |-- pages | | | |-- home | | | |-- accounts | | | `-- ... | | `-- widgets | | `-- account-table | | `-- account-chart | | `-- ... | |-- framework | | |-- Baseview.js | | |-- Router.js | | `-- ... | `-- vendor | |-- backbone.js | |-- jquery.js | `-- ... |-- sass |-- img `-- index.html

60+ JavaScript files 2000 lines of code But much easier to find things!

JavaScript and templates (markup)

JavaScript and templates (markup)

Defining a Module

RequireJS loads all code relative to a baseUrl (set to the same directory as the script used in a data-main)

// app/widgets/login/LoginWidget // Defines a widget called LoginWidget define( [], function() { return BaseView.extend({ ... }); } );

Using a Module

Defining dependency on a module makes sure that the module is pulled in before the dependent code.

// app/pages/home/HomePage // Defines the home page that uses the LoginWidget define( ['app/widgets/login/LoginWidget'], function(LoginWidget) { return BaseView.extend({ postRender: function() { this.addChild({ id: 'LoginWidget', viewClass: LoginWidget, parentElement: this.$el }); } ... }); } );

Moving templates into text modules

<td class="name"> {{name}} </td> <td class="market-value"> {{formatMoney marketValue}} </td> <td class="cash"> {{formatMoney cashPosition}} </td> ...

AccountTemplate.html

Using templates defined in text modules

define( ['text!app/widgets/account-table/AccountTemplate.html'], function(AccountTemplate) { return BaseView.extend({ template: { name: 'AccountTemplate', source: AccountTemplate }, ... }); } );

AccountView.js

Optimizing for Production – The Build System

• A web application consisting of hundreds of JavaScript files could have severe performance implications in a production environment

• RequireJS comes with an optimizer that walks the dependency tree and concatenates all the required JavaScript files into one

• The Bullsfirst build system runs the RequireJS optimizer plus other optimizations such as yuicompressor and uglifyjs to reduce the download size of the application – The build system is based on Grunt.js, a task-based command line build tool for

JavaScript projects

– It cuts down application load time to approximately 2 seconds on a 3 Mbps network

Summary

Summary

• Use the MVC pattern to enforce separation of concerns

• Compose your application using smaller Reusable Components

• Create a Domain Layer as a central place for views to access information

• Use In-Application Messaging to decouple application components

• Use Modules to break large applications into bite-size chunks

A strong architecture is key to building engaging web applications

References

References

• Backbone.js – https://github.com/documentcloud/backbone

• RequireJS – https://github.com/jrburke/requirejs

• Bullsfirst is an open-source project under http://archfirst.org – Sharing best practices with the developer community

– Live demo at http://archfirst.org/apps/bullsfirst-jquery-backbone

– Source repository: https://github.com/archfirst/bullsfirst-jquery-backbone

– Discussion/feedback at http://archfirst.org/forums/general-discussion

43