By Brian Sam-Bodden
with jqm, spine.mvc, sinatra and mustache
The Road to Mobile Web Development
1
WHAT I HAVE FOR YOU...
2
• A possible path to mobile web applications:
• Sinatra + Mustache for a Simpler Server-Side• jQuery Mobile for Out-of-the-box Mobile• Spine.js for MVC goodness on the Client-Side• Where to start? Front-to-Back or Back-to-Front?
2
THE MOBILE WEBAGENT COMPUTING COMES OF AGE
3
“...the future of computing is mobile... businesses should have their best developers working on their mobile
applications”Google’s Eric Schmidt Interview on TechCrunch, April 2010
4
MOBILE WEBAGENT COMPUTING COMES OF AGE
4
• 20%: decline of home PC usage since 2008 (1)
• 74%: increase of smart phones sold from 2010 to 2011(2)
• 261%: increase in smart tablets sold from 2010 to 2011(3)
• $9 billion: predicted US mobile shopping sales in 2011 (4)
• 10.8%: people who used a mobile device to visit a retailer’s site on Cyber Monday (up from 3.9% in 2010) (4)
MOBILE WEBSOME STATISTICS
5
5
“Manufacturers shipped more smartphones than personal
computers in the fourth quarter of 2010... crowning
mobile devices as the computing platform of choice.
Financial Times Article, February 2011
6
MOBILE WEBMOVE OVER PCS
6
• In a Mall Kiosk in Ft. Lauderdale, Florida:
7
MOBILE WEBSEEN IN UNUSUAL PLACES
7
8
9
HTML5WHAT YOU NEED TO KNOW
Big Picture: Canvas, Video, Geolocation and Offline Web Apps
9
10
HTML5WHAT YOU NEED TO KNOW
Big Picture: IE Still Sucks! Who owns a windows phone anyways? :-)
10
• Simplified header and doctype: <!DOCTYPE html>
• Script tag doesn’t need type attribute
• Many more semantic tags added like header, nav, section, article, aside, footer, details, summary, address, figure, output, time, pubdate, nav, first, last, next, prev, menu, command, mark, strong, progress, meter and many, many more
• Data attributes (data-*)
• Media Tags
HTML5WHAT YOU NEED TO KNOW
11
11
• Native GeoLocation API for Mobile Browsers
• 2D Graphics with Canvas
• Many new form input types
• Datalist
• Markup-driven form validation
• Local Storage, Web Sockets, Web Workers
HTML5WHAT YOU NEED TO KNOW
12
12
THE ROAD TO MOBILEBROWSER-BASED OR NATIVE?
13
WEB OR NATIVE?MAKING AN INFORM DECISION
14
• Number of Mobile Web Apps have quickly surpassed their native counterparts
14
WEB OR NATIVE?MAKING AN INFORM DECISION
15
Taptu predicts that "the Mobile Touch Web will grow... and will approach the quality .. of [Native] Apps across all the
app categories except for games."
From “Mobile App or Browser-Based Site? Report Says The Browser Will Win on Mobile”February 2010, ReadWriteWeb.com
15
WEB OR NATIVE?MAKING AN INFORM DECISION
16
Factor Native Web
# of target devices
More Devices == More Projects, Inconsistencies between products
One codebase (with small tweaks) to rule them all
distribution Application Stores Processes/Charge Upfront :-) Instant
time to market Slow! Faster for simpler applications
developer skills Specialized UI developer skills: iOS, Android, etc. HTML/JavaScript/CSS + Frameworks
multimedia Audio/Video/ 2D and 3D graphics more accessible from native APIs
HTML 5 Video / Canvas coming along, WebGL future uncertain(1)
device integrationTouch Gestures / Accelerometer / Gyroscope / Camera / Geo-
location / File System / System Alerts more/only accessible from native APIs
Hacks abound to make some of these work
audiencegamers, selective downloaders (only download if app provides significant advantages of others) and the “only if I can have an
icon for it” crowd
anybody with a browser that it is inclined to use it!
16
• If your application core relies on 3D, File System, Camera, Mobile Security, System Alerts, Marketplace, Performance go Native
• If your application success hinges on ease of deployment and distribution, deeply linked pages, social media and constant updates/changes then go Web
• Factors that are improving and/or are close to match their native counterparts include audio, video, 2D graphics, input and gestures, accelerometer and gyroscope, geolocation and native-like application launcher
WEB OR NATIVE?MAKING AN INFORM DECISION
17
17
WEB OR NATIVE?MAKING AN INFORM DECISION
18
... and there is always the hybrid approach!
build it for the Web, convert it to Native
http://rhomobile.com/https://webmynd.com/http://phonegap.com/http://appcelerator.com/
18
DEVELOPMENT APPROACHWHERE TO START?
19
• Server-side developers gravitate to fleshing out the API first
• Client-side developers start with the look and feel
• After trying both I’ve decided that it is best to start at both ends and meet in the middle :-)
APPROACHWHERE TO START?
20
20
SINATRATHE LIGHTWEIGHT HTTP RUBY DSL
21
• Why?
• Rails might be too heavy for our purposes
• You will/might need to support native clients
• API-driven and JSON is the payload
SINATRAA LIGHTWEIGHT SERVER SIDE ALTERNATIVE
22
22
• Multiple mini-apps in one process...
SINATRAA LIGHTWEIGHT SERVER SIDE ALTERNATIVE
23
map '/' do run Comida::ComidaWebAppend
map '/api' do run Comida::ComidaApiend
module Comida # # The Web App # class ComidaWebApp < Sinatra::Base ... end # # The API # class ComidaApi < Sinatra::Base ... endend
23
• I wanted simple templates that I could render on the client and on the server
• No Ruby in my views, no markup in my Ruby!
MUSTACHELOGIC-LESS TEMPLATES
24
24
• Classy with logic less templates...
SINATRA W/ MUSTACHEA LIGHTWEIGHT SERVER SIDE ALTERNATIVE
25
25
• Render a mustache template (with a layout too).
26
class ComidaWebApp < Sinatra::Base register Mustache::Sinatra require './views/layout'
get '/' do mustache :search end
{{> search_page}}
{{> search_results_page}}
<script type="text/javascript" src="javascripts/search.js"></script>
template/search.mustache
<section data-role="page" id="search_results"> <header data-role="header"> <h1>Resturants</h1> <nav data-role="navbar"> <ul> <li><a href="#home" class="ui-btn-active">Search</a></li> <li><a href="#menu">Menus</a></li> <li><a href="#order">Order</a></li> </ul> </nav> </header> <div data-role="content" id="content_main"> <form action="/api/menus.json" method="get" data-ajax="false" id="restaurant_selection_form"> <div id="restaurants_found"></div> <button type="submit" data-theme="a">Submit</button> </form> </div></section><!-- /page -->
SINATRA W/ MUSTACHEA LIGHTWEIGHT SERVER SIDE ALTERNATIVE
template/search_result_page.mustache
26
• Simple API with Sinatra...
SINATRAA LIGHTWEIGHT SERVER SIDE ALTERNATIVE
27
get '/search.json' do response = {} response[:restaurants] = restaurants content_type :json response.to_jsonend
• Let’s explore the sample’s app API with IRB and CURL
27
JQUERY MOBILEA JQUERY-DRIVEN USER INTERFACE FRAMEWORK
28
• A lightweight markup-driven User Interface (UI) framework for mobile web applications
• Build on JavaScript and making extensive use of the jQuery JavaScript Library (about 12K minified)
• Promotes the use of clean, semantic HTML that gets enhanced progressively and degrades gracefully if needed
• Supports a large variety of hardware and device features
• Supports Accessible Rich Internet Applications (WAI-ARIA)
• Theme-able following the ThemeRoller philosophy of jQuery UI
29
JQUERY MOBILEINTRODUCTION
29
• To start on your path to web development for mobile devices you’ll need:
• Editor: An editor capable of dealing with HTML, CSS and JavaScript and potentially with your server-side language/platform/framework of choice
• Emulation/Simulation: A way to emulate the different devices and resolutions that your application/website wants to target
• Debugging: For more complex applications the ability to debug, trace and analyze JavaScript code
30
JQUERY MOBILESETTING YOUR DEVELOPMENT ENVIRONMENT
30
SIMULATION, EMULATION & LIVE TESTING
WHAT TO DO WHEN
31
• The Mobile Web Development cycle involves coding, previewing often on a regular desktop/laptop web browser and interspersed testing on a particular device emulator or on the target device itself* (particularly to test the feel of any gesture based interaction, device orientation changes and
other features that can only be experienced on the device itself)
• The simplest way to test is to run a local Web Server on your development machine and access the application over a WIFI network
32
JQUERY MOBILEEMULATION CHOICES
32
• The Android SDK (http://developer.android.com) includes a device emulator that can support many Android Virtual Devices (AVDs).
33
JQUERY MOBILETHE ANDROID EMULATOR
33
• The iOS Simulator (http://developer.apple.com/programs/ios/) is tucked away in Apple’s free iPhone Software Development Kit
34
JQUERY MOBILETHE ANDROID EMULATOR
The iOS simulator can be found under /Developer/Platforms/iPhoneSimulator.platform/Developer/Applications and it is aptly named “iOS Simulator”
34
PAGESTHE BUILDING BLOCKS OF A MOBILE APPLICATION
35
• Let’s open the example_1.html page in Safari (5.1.2 shown below) at 1024x768:
36
part_1/example_1.html
jQuery Mobile applications can also be
used on desktop, laptops and tablets
JQUERY MOBILEEMULATING A DEVICE
36
• In Safari (5.1.2) at 640x960 (iPhone 4 resolution):
37
part_1/example_1.html
The iPhone, iPad and iPods use a mobile version of Safari so desktop Safari at iPhone/
iPad resolutions is a perfect browser for rapid development
JQUERY MOBILEEMULATING A DEVICE
37
• On the Android Emulator :
38
part_1/example_1.html
JQUERY MOBILEEMULATING A DEVICE
38
• On the iOS Emulator :
39
part_1/example_1.html
JQUERY MOBILEEMULATING A DEVICE
39
• On the iPhone 4:
40
part_1/example_1.html
JQUERY MOBILEEMULATING A DEVICE
40
• On the iPad
41
part_1/example_1.html
JQUERY MOBILEEMULATING A DEVICE
41
• The basic document contains a single Page with a Title and a Body showing some simple content
• jQuery Mobile Documents can contain one or more mobile pages as well explore later in the course
42
part_1/example_1.html
JQUERY MOBILEEMULATING A DEVICE
42
• The HTML document source is shown below:
43
JQUERY MOBILEGETTING STARTED
<!DOCTYPE html> <html> <head> <title>Browser Page Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css" /> <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script> <script type="text/javascript" src="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js"></script></head> <body>
<div data-role="page">
<div data-role="header"> <h1>Page Title</h1> </div><!-- /header -->
<div data-role="content"> <p>This is the content</p> </div><!-- /content -->
</div><!-- /page -->
</body></html>
part_1/example_1.html
43
• The body of the document consists of several div elements
• The div are decorated with HTML5 data-role attributes
• The outer div has a role of page and contains two stacked inner divs with the roles header and content
44
JQUERY MOBILEGETTING STARTED
<body> <div data-role="page">
<div data-role="header"> <h1>Page Title</h1> </div><!-- /header -->
<div data-role="content"> <p>This is the content</p> </div><!-- /content -->
</div><!-- /page -->
</body></html>
part_1/example_1.html
44
45
part_1/example_1.html
JQUERY MOBILEEMULATING A DEVICE
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css" />
• The link tag applies the jQuery Mobile CSS stylesheet to the document
• Commenting out the CSS reveals the simplicity of the underlying markup
45
MULTI-PAGE DOCUMENTSINLINE PAGES
46
47
MULTI-PAGE DOCSA DOCUMENT WITH A COUPLE OF PAGES
<div data-role="page" id="home">
<div data-role="header"> <h1>Home</h1> </div><!-- /header -->
<div data-role="content"> <p>This is the home page</p> </div><!-- /content --> <div data-role="footer" data-position="fixed"> <h4>The Footer</h4> </div><!-- /footer -->
</div><!-- /page -->
<div data-role="page" id="other">
<div data-role="header"> <h1>Other</h1> </div><!-- /header -->
<div data-role="content"> <p>This is another page</p> </div><!-- /content --> <div data-role="footer" data-position="fixed"> <h4>The Footer</h4> </div><!-- /footer -->
</div><!-- /page -->part_1/example_2.html
47
48
MULTI-PAGE DOCSLOCATION HASH BASED NAVIGATION
http://.../part_1/example_2.html http://.../part_1/example_2.html#other
• HTML5 provides the window.location.hash which is exploited by jQuery Mobile to provide navigation to individual pages within a multi-page document
• Adding #other to the URL will navigate to the “other” page using the default transition (slide)
48
49
MULTI-PAGE DOCSLINKING PAGES
<div data-role="page" id="home">
<div data-role="header"> <h1>Home</h1> </div><!-- /header -->
<div data-role="content"> <p>This is the home page</p> <p>Check out this <a href="#other">other</a> page</p> </div><!-- /content --> <div data-role="footer" data-position="fixed"> <h4>The Footer</h4> </div><!-- /footer -->
</div><!-- /page -->
• Let’s fill add navigation links to the other page from the home page and vice-versa:
part_1/example_2.html
<div data-role="page" id="other">
<div data-role="header"> <h1>Other</h1> </div><!-- /header -->
<div data-role="content"> <p>This is another page</p> <p>Take me <a href="#home">back!</a></p> </div><!-- /content --> <div data-role="footer" data-position="fixed"> <h4>The Footer</h4> </div><!-- /footer -->
</div><!-- /page -->
49
50
MULTI-PAGE DOCSBACK LINKS
• A generic way to create a link to the previous page (without adding a new entry to the browser history) is to use the data-rel= “back” attribute as shown below:
part_1/example_2.html
<div data-role="page" id="other">
<div data-role="header"> <h1>Other</h1> </div><!-- /header -->
<div data-role="content"> <p>This is another page</p> <p>Take me <a href="#home" data-rel="back">back!</a></p> </div><!-- /content --> <div data-role="footer" data-position="fixed"> <h4>The Footer</h4> </div><!-- /footer -->
</div><!-- /page -->
50
51
MULTI-PAGE DOCSA DOCUMENT WITH A COUPLE OF PAGES
• We can now navigate from #home to #other and back:
part_1/example_2.html
51
52
PAGE TRANSITIONSANIMATION BASED NAVIGATION
<div data-role="content"> <p>This is the home page</p> <p>Check out this <a href="#other" data-transition="flip">other</a> page</p> </div><!-- /content -->
• So far we have seen the default ‘slide’ transition when navigating from page to page
• jQuery Mobile provides several transitions that we can apply via the data-transition attribute
• Let’s modify the link to the “other” page to use the “flip” transition animation
part_1/example_2.html
52
53
PAGE TRANSITIONSANIMATION BASED NAVIGATION
• The ‘flip’ transition animation in action:
part_1/example_2.html
53
54
BUTTONSSTYLIZED BUTTONS
<div data-role="page" id="other">
<div data-role="header"> <a href="#home" data-role="button" data-rel="back" data-icon="back" >Back</a> <h1>Other</h1> </div><!-- /header -->
part_1/example_2.html
• jQuery Mobile provides a “button” data-role attribute that can be applied to a link to turn it into a nicely styled button
• Let’s add a back button link to our “other” page
54
55
BUTTONSSTYLIZED BUTTONS
• The newly added back button:
55
56
LIST VIEWSSTYLING ORDERED OR UNORDERED LISTS
<div data-role="page" id="other"> ... <div data-role="content"> <h3>Unordered List</h3> <ul data-role="listview"> <li>Apples</li> <li>Oranges</li> <li>Peaches</li> </ul> <h3>Ordered List</h3> <ol data-role="listview"> <li>Woke up</li> <li>Fell out of bed</li> <li>Dragged a comb across my head</li> </ol> </div><!-- /content --> ...</div><!-- /page -->
part_1/example_2.html
• jQuery Mobile provides a “listview” data-role attribute that can turn an ordered or unordered list into a nicely styled native looking list
• Let’s modify the “other” page to show a couple of lists
56
57
• The styled lists on the ‘other’ page:
LIST VIEWSSTYLING ORDERED OR UNORDERED LISTS
57
• The beginnings of a mobile website for a restaurant
• On the home page we want to display the restaurant’s name and a logo alongside some welcome text and a button link to a “menu” page
• On the menu page we will display a list of menu items and a back button to the home page
58
DEMO 1.0LAUNCHING THE RESTAURANT MOBILE SITE
58
APPLICATION INITIALIZATION
USING THE JQUERY PLUGIN PATTERN
59
60
INITIALIZATIONUSING THE JQUERY PLUGIN PATTERN WITH JQUERY MOBILE
(function($) { var methods = { initPageOne : function(options) { }, initPageTwo : function(options) { }, initAllPages : function(options) { $().initApp("initPageOne"); $().initApp("initPageTwo"); } } $.fn.initApp = function(method) { // Method calling logic if ( methods[method] ) { return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.initAllPages.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist' ); } }})(jQuery);
60
• Finally we can initialize the application by calling the initApp function from within a document ready handler function:
61
INITIALIZATIONUSING THE JQUERY PLUGIN PATTERN WITH JQUERY MOBILE
<script>$(document).ready(function() { $().initApp();})</script>
61
MVC IN JAVASCRIPTINTRODUCING SPINE.JS
62
SPINE.JSMVC FOR JAVASCRIPT
63
• Spine.js is a small library provides a micro-MVC framework for your JavaScript applications
• It allow you to create a separation between the model (business JavaScript code) and the view (which renders the DOM)
• When models change their associated views are re-rendered without tightly coupling them to the DOM
• Spine.js keeps state/model in a single space, model changes propagate automatically to the views with very little glue code
64
SPINE.JSMVC FOR JAVASCRIPT
64
JQUERY SHOPPING CART
65
• Let’s walk through the code of a jQuery powered shopping cart from the ground up
• The markup consists of two lists styled to appear side-by-side and an element showing the total amount of the items in the shopping cart
• Each retail item will be represented by a div containing an image (drag handle), encode the item price in the div
• As items are dragged into the cart, use an effect to alert the user of the cart total amount changes
• The non-visual JS code uses the prototype pattern to separate the UI interactions from the business interactions
• As items are dropped into the cart, add controls to the items to increase the quantity
66
JQUERY SHOPPING CARTDRAG AND DROP SHOPPING CART
66
• The jQuery powered Shopping Cart project structure is shown below:
67
JQUERY SHOPPING CARTDRAG AND DROP SHOPPING CART
67
• jQuery Shopping Cart in action:
68
JQUERY SHOPPING CARTDRAG AND DROP SHOPPING CART
68
• At first glance the implementation of the shopping cart seems to work as advertised and even seems well thought- out. That is, until we start asking the hard questions.
• Shortcoming of the jQuery Shopping Cart:
• From the usability point of view, the first thing that jumps to mind is, what happens when the user clicks the refresh button?
• Since everything is just being kept in memory the answer is that it simply goes away
69
JQUERY SHOPPING CARTDRAG AND DROP SHOPPING CART
69
• Shortcoming of the jQuery Shopping Cart (cont.):
• For anything more complex we would end up with an unmanageable mess of UI callbacks all of which are currently living in the global JavaScript scope.
• Another problem glaring problem is the coupling of the concepts of the shopping cart and the items in the shopping cart.
• The items are simple JavaScript objects with no behavior, their reflection on the UI is controlled by the decorateForCart function
70
JQUERY SHOPPING CARTDRAG AND DROP SHOPPING CART
70
SPINE.JSMVC FOR JAVASCRIPT
• Spine.js is partly based on Backbone’s API yet Spine’s take on the MVC pattern is slightly different
• I used Spine.js to refactor the jQuery Shopping Cart
• Problems I wanted to fix on the shopping cart solution:
• Using the back button or the refresh button loses the carts contents
• UI callbacks and manual event handling and triggering can quickly get out of hand
71
71
SPINE.JSMVC FOR JAVASCRIPT
• Models: In Spine, models are created using the setup method of Spine.Model, which takes the name of the model and an array of properties
72
// Create the Item model.var Item = Spine.Model.sub();Item.configure("Item", "name", "pid", "price", "quantity");
part_2/examples/jquery-spine-shopping-cart/app/models/item.js
72
SPINE.JSMVC FOR JAVASCRIPT
• To make the model persists between page reloads we extend the Item with the Spine.Model.Local module
73
// Persist model between page reloads.Item.extend(Spine.Model.Local);
part_2/examples/jquery-spine-shopping-cart/app/models/item.js
• The extend method adds class properties to the model. We now have an object that can be created, saved and retrieved from the browser local storage.
73
SPINE.JSMVC FOR JAVASCRIPT
• To add behavior to our model we use the include method which adds instance properties
• The four methods that we need for our Item model; increase, decrease, total and label
74
// Instance methodsItem.include({ // total: function() { return (this.price * this.quantity); }, // increase: function(quantity) { quantity = (typeof(quantity) != 'undefined') ? quantity : 1; this.quantity = this.quantity + quantity; this.save(); }, // decrease: function(quantity) { quantity = (typeof(quantity) != 'undefined') ? quantity : 1; if (this.quantity >= quantity) { this.quantity = this.quantity - quantity; } else { this.quantity = 0; } this.save(); }, // label: function() { return (this.name + " - $" + this.price); } });
part_2/examples/jquery-spine-shopping-cart/app/models/item.js
74
SPINE.JSMVC FOR JAVASCRIPT
• To instantiate an Item we use the create method which takes an object literal for the parameters. A new model can be persisted using the save method. When a model is saved it is assigned an identifier that can be retrieved via the id property.
75
var item = new Item({name: "Product 1", pid: "0001" , price: 100.0, quantity: 1});item.save();alert("Just saved Item with id => " + item.id);
75
SPINE.JSMVC FOR JAVASCRIPT
• Controllers: Controllers in Spine are a combination of a traditional MVC controller and a view. Therefore controllers are in charge of rendering and manipulating one or more models in the context of controller’s functionality
• Our first Spine controller will deal with the rendering and manipulation of an individual Item. The CartItem controller will deal with user interface events to increase and decrease the quantity of a Item while keeping the user abreast of the changes.
76
76
SPINE.JSMVC FOR JAVASCRIPT
• Spine controllers like the CartItem are created using the create method of Spine.Controller, which takes an object literal that a wide variety of properties
77
jQuery(function($){ window.CartItem = Spine.Controller.sub({ init: function(){ var cartItem = this; this.item.bind("quantityChanged", function() { cartItem.updateQty() }); $('#item_' + this.item.pid + ' .add').live('click', function(e) { cartItem.add(); e.preventDefault(); }); $('#item_' + this.item.pid + ' .remove').live('click', function(e) { cartItem.remove(); e.preventDefault(); }); },
part_2/examples/jquery-spine-shopping-cart/app/controllers/cart_item.js
77
SPINE.JSMVC FOR JAVASCRIPT
• ... cont.
78
render: function(){ this.el = $.mustache($("#cartItem").html(), this.item); return this; }, // event handlers add: function(e) { this.item.increase(); }, remove: function(e) { this.item.decrease(); }, // ui methods updateQty: function() { $('#item_' + this.item.pid + ' #qty') .text(this.item.quantity) .effect("highlight", {}, 1500); } });})
part_2/examples/jquery-spine-shopping-cart/app/controllers/cart_item.js
78
SPINE.JSMVC FOR JAVASCRIPT
• The cartItem template is a Mustache.js Template that enables the creation of markup templates containing binding expressions. The mustache method clones the template contents and replaces the binding expressions ({{exp}}) with the values of the object passed, in our case the controller’s enclosed Item model.
79
<!-- Mustache :-{)~ Template for CartItem --><script type="text/x-mustache-tmpl" id="cartItem"> <li class="product ui-state-default" id="item_{{pid}}" price="{{price}}"> {{label}} (<span id="qty">{{quantity}}</span>) <a href="#" class="add">+</a> <a href="#" class="remove">-</a> </li></script>
part_2/examples/jquery-spine-shopping-cart/shopping_cart.html
79
SPINE.JSMVC FOR JAVASCRIPT
• To test our controller we need to instantiate an Item model and use it to instantiate the controller. We can then render the controller on the DOM of an HTML page
80
var item = new Item({name: "Product 1", pid: "0001" , price: 100.0, quantity: 1});var view = new CartItem({item: item});$("#item").html(view.render().el);
part_2/examples/jquery-spine-shopping-cart/tests/cart_item_test.html
80
SPINE.JSMVC FOR JAVASCRIPT
• The ShoppingCart controller which will manage a collection of Item models. Internally the ShoppingCart keeps the items dropped in the items property
• The clear, total, isEmpty and itemsCount methods fulfill the business functionality of the cart
81
jQuery(function($){ window.ShoppingCart = Spine.Controller.sub({ el: $("#theCart"), init: function() { var cart = this; this.items = {}; $.each(Item.all(), function(){ cart.addItem(this); }); this.el.droppable({ accept: '.product', drop: this.proxy(this.drop) }); $('#dump', this.el).live('click', function() { cart.clear(); }); }, // removes all items from the cart clear: function() { $.each(this.items, function(){ this.destroy(); }); this.items = {}; this.updateCartTotal(); }, total: function() { var sum = 0.0; $.each(this.items, function(){ sum += this.total(); });
return sum; }, isEmpty: function() { return this.itemsCount() == 0; }, itemsCount: function() { var size = 0; var items = this.items; $.each(items, function(){ if (items.hasOwnProperty(this)) size++; });
return size; }, ...
part_2/examples/jquery-spine-shopping-cart/app/controllers/shopping_cart.js
81
SPINE.JSMVC FOR JAVASCRIPT
82
...drop: function(ev, ui) { var item_dropped = ui.draggable; var pid = item_dropped.attr('id'); var price = item_dropped.attr('price'); var name = item_dropped.attr('name');
if (this.items.hasOwnProperty(pid)) { this.items[pid].increase(); } else { var item = Item.create({name: name, pid: pid, price: price, quantity: 1}); this.addItem(item); $(".items").append(CartItem.init({item: item}).render().el); }},
part_2/examples/jquery-spine-shopping-cart/app/controllers/shopping_cart.js
• The drop method handles drop events; it either creates a new item or increases the quantity of an existing item. It creates an Item model that is then rendered using the CartItem controller
82
SPINE.JSMVC FOR JAVASCRIPT
83
...render: function() { this.el.html($.mustache($("#shoppingCart").html(), {})); $('#dump').button();
$.each(this.items, function(){ $(".items").append(CartItem.init({item: this}).render().el); }); this.updateCartTotal();},
part_2/examples/jquery-spine-shopping-cart/app/controllers/shopping_cart.js
• The render method render the #shoppingCart template, decorates the #dump button and loops through the contained Items creating a CartItem for each and rendering them in the .items
83
SPINE.JSMVC FOR JAVASCRIPT
• The removeItem, updateCartTotal, removeIfQuantityZero and addItem deal with responding to UI events and updating the UI
84
...removeItem: function(item) { $('#item_' + item.pid).effect("puff", {}, "slow", function(){ $(this).remove(); });},
updateCartTotal: function() { $('#total').text(this.total()).effect("highlight", {}, 1500);},
removeIfQuantityZero: function(item) { if (item.quantity == 0) { this.removeItem(item); delete this.items[item.pid]; item.destroy(); }},
addItem: function(item) { this.items[item.pid] = item; item.bind("quantityChanged", this.proxy(this.updateCartTotal)); item.bind("quantityChanged", this.proxy(this.removeIfQuantityZero));item.bind("quantityChanged", function() { item.save() }); item.bind("destroy", this.proxy(this.removeItem)); item.save(); this.updateCartTotal();}
part_2/examples/jquery-spine-shopping-cart/app/controllers/shopping_cart.js
84
SPINE.JSMVC FOR JAVASCRIPT
85
part_4/examples/jquery-spine-shopping-cart/app/application.js
• Finally to kick everything in motion the file application.js file begins by fetching any previously stored Item records, making the sample products draggable, creating a cart and rendering it.
jQuery(function($){ Item.fetch(); $(".product").draggable({ helper: 'clone', opacity: "0.5" }); var cart = new ShoppingCart(); cart.render();});
85
RESOURCES
86
RESOURCES• Sinatra: http://www.sinatrarb.com/
• jQuery Mobile: http://jquerymobile.com
• Spine.js: http://spinejs.com
• Example Code:
• https://github.com/bsbodden/jquery-shopping-cart
• https://github.com/bsbodden/jquery-spine-shopping-cart
• https://github.com/integrallis/jqm-spine-sinatra-demo
87
87