Node Powered Mobile
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Node Powered Mobile

on

  • 5,744 views

 

Statistics

Views

Total Views
5,744
Views on SlideShare
5,563
Embed Views
181

Actions

Likes
5
Downloads
68
Comments
1

6 Embeds 181

http://coderwall.com 129
http://www.slideshare.net 26
http://www.linkedin.com 18
http://speakerrate.com 6
http://bl157w.blu157.mail.live.com 1
https://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Node Powered Mobile Presentation Transcript

  • 1. Node Powered Mobile By Tim Caswell Saturday, June 5, 2010
  • 2. Node Powered Mobile By Tim Caswell Saturday, June 5, 2010
  • 3. Simple but Different Saturday, June 5, 2010
  • 4. What is needed Saturday, June 5, 2010
  • 5. What is needed • Simple Interface • Light Code • Networked Data • Real-Time Data • Free Deployment • Open Workflow Saturday, June 5, 2010
  • 6. What is needed • Simple Interface • HTML, SVG, CSS • Light Code • JavaScript • Networked Data • HTTP Services • Real-Time Data • PubSub • Free Deployment • Browser Apps • Open Workflow • It’s just text! Saturday, June 5, 2010
  • 7. Connect We’ll use a new node framework that “connects” the mobile browser to data on the server. Saturday, June 5, 2010
  • 8. It’s like Japanese Legos Saturday, June 5, 2010
  • 9. Pre-Built Blocks Connect.createServer([ {filter: "log"}, {filter: "body-decoder"}, {filter: "conditional-get"}, {filter: "cache"}, {filter: "gzip"}, {provider: "cache-manifest", root: root}, {provider: "static", root: root} ]); Saturday, June 5, 2010
  • 10. And easy too! Saturday, June 5, 2010
  • 11. method-override.js var key; // Initialize any state (on server startup) exports.setup = function (env) { key = this.key || "_method"; }; // Modify the request stream (on request) exports.handle = function(err, req, res, next){ if (key in req.body) { req.method = req.body[key].toUpperCase(); } next(); }; Saturday, June 5, 2010
  • 12. response-time.js exports.handle = function(err, req, res, next){ var start = new Date, writeHead = res.writeHead; res.writeHead = function(code, headers){ res.writeHead = writeHead; headers['X-Response-Time'] = (new Date - start) + "ms"; res.writeHead(code, headers); }; next(); }; Saturday, June 5, 2010
  • 13. Well, actually, it’s not always easy. Saturday, June 5, 2010
  • 14. static.js Saturday, June 5, 2010
  • 15. static.js var fs = require('fs'), Url = require('url'), Path = require('path'); var lifetime = 1000 * 60 * 60; // 1 hour browser cache lifetime var DEFAULT_MIME = 'application/octet-stream'; module.exports = { setup: function (env) { this.root = this.root || process.cwd(); }, handle: function (err, req, res, next) { // Skip on error if (err) { next(); return; } var url = Url.parse(req.url); var pathname = url.pathname.replace(/..+/g, '.'), filename = Path.join(this.root, pathname); if (filename[filename.length - 1] === "/") { filename += "index.html"; } Saturday, June 5, 2010
  • 16. static.js // Buffer any events that fire while waiting on the stat. var fs = require('fs'), var events = []; Url = require('url'), function onData() { Path = require('path'); events.push(["data"].concat(Array.prototype.slice.call(arguments))); } var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() { browser cache lifetime events.push(["end"].concat(Array.prototype.slice.call(arguments))); } var DEFAULT_MIME = 'application/octet-stream'; req.addListener("data", onData); module.exports = { req.addListener("end", onEnd); setup: function (env) { fs.stat(filename, function (err, stat) { this.root = this.root || process.cwd(); }, // Stop buffering events req.removeListener("data", onData); handle: function (err, req, res, next) {req.removeListener("end", onEnd); // Skip on error if (err) { // Fall through for missing files, thow error for other problems next(); if (err) { return; if (err.errno === process.ENOENT) { } next(); var url = Url.parse(req.url); // Refire the buffered events events.forEach(function (args) { req.emit.apply(req, args); var pathname = url.pathname.replace(/..+/g, '.'), }); filename = Path.join(this.root, pathname); return; if (filename[filename.length - 1] === "/") { filename += "index.html"; } Saturday, June 5, 2010
  • 17. static.js // Buffer any events that fire while waiting on the stat. var fs = require('fs'), var events = []; Url = require('url'), function onData() { Path = require('path'); events.push(["data"].concat(Array.prototype.slice.call(arguments))); } var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() { browser cache lifetime events.push(["end"].concat(Array.prototype.slice.call(arguments))); } var DEFAULT_MIME = 'application/octet-stream'; req.addListener("data", onData); module.exports = { req.addListener("end", onEnd); (err); setup: function (env) { fs.stat(filename, function (err, stat) { rn; this.root = this.root || process.cwd(); }, // Stop buffering events req.removeListener("data", onData); req.removeListener("end", onEnd); the file directly using (err, req, res, next) { handle: function buffers ile(filename, function error data) { // Skip on (err, if (err) { // Fall through for missing files, thow error for other problems err) { next(); if (err) { next(err); return; if (err.errno === process.ENOENT) { return; } next(); var url = Url.parse(req.url); // Refire the buffered events writeHead(200, { events.forEach(function (args) { ontent-Type": Mime.type(filename), req.emit.apply(req, args); ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), var data.length, }); filename = Path.join(this.root, pathname); ast-Modified": stat.mtime.toUTCString(), return; / Cache in browser for 1 year ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 filename += "index.html"; end(data); } Saturday, June 5, 2010
  • 18. static.js // Buffer any events that fire while waiting on the stat. var fs = require('fs'), var events = []; Url = require('url'), function onData() { Path = require('path'); events.push(["data"].concat(Array.prototype.slice.call(arguments))); } var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() { browser cache lifetime }; events.push(["end"].concat(Array.prototype.slice.call(arguments))); } var DEFAULT_MIME = 'application/octet-stream'; // Mini mime module for static file serving req.addListener("data", onData); module.exports = { var req.addListener("end", onEnd); Mime = { (err); type: function getMime(path) (err, stat) { setup: function (env) { fs.stat(filename, function { rn; var index = path.lastIndexOf("."); this.root = this.root || process.cwd(); }, if (index < buffering events // Stop 0) { return DEFAULT_MIME; req.removeListener("data", onData); } req.removeListener("end", onEnd); the file directly using (err, req, res, next) { type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME; handle: function buffers var ile(filename, // function error data) { Skip on (err, if (err) { return Fall through for missing files, thow error "; charset=utf-8" : type; // (/(text|javascript)/).test(type) ? type + for other problems err) { }, next(); if (err) { next(err); return; if (err.errno === process.ENOENT) { return; TYPES : { ".3gp" } next(); "video/3gpp", : var url = Url.parse(req.url); ".a" Refire the buffered events // : "application/octet-stream", writeHead(200, { ".ai" : "application/postscript", events.forEach(function (args) { ontent-Type": Mime.type(filename), ".aif" req.emit.apply(req, args); : "audio/x-aiff", ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff", var data.length, ".aiff" : }); filename = Path.join(this.root, pathname); ast-Modified": stat.mtime.toUTCString(), ".asc" return; "application/pgp-signature", : / Cache in browser for 1 year ".asf" : "video/x-ms-asf", ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 ".asm" : "text/x-asm", filename += "index.html"; ".asx" : "video/x-ms-asf", end(data); } ".atom" : "application/atom+xml", ".au" : "audio/basic", Saturday, June 5, 2010 ".avi" : "video/x-msvideo",
  • 19. static.js // Buffer any events that fire while waiting on the stat. var fs = require('fs'), : "video/x-flv",var events = []; ".flv" ".for" : function onData() { Url = require('url'), "text/x-fortran", Path = require('path'); ".gem" events.push(["data"].concat(Array.prototype.slice.call(arguments))); : "application/octet-stream", } ".gemspec" : "text/x-script.ruby", var lifetime = ".gif" 60: *"image/gif", function onEnd() { 1000 * 60; // 1 hour browser cache lifetime }; events.push(["end"].concat(Array.prototype.slice.call(arguments))); ".gz" : "application/x-gzip", ".h" : "text/x-c", } var DEFAULT_MIME = 'application/octet-stream'; // Mini mime module for static file serving req.addListener("data", onData); ".hh" : "text/x-c", module.exports ".htm" = { var Mime = { : "text/html", req.addListener("end", onEnd); (err); ".html" : "text/html", rn; setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) { ".ico" : { type: function fs.stat(filename, function { var index = path.lastIndexOf("."); this.root = this.root || process.cwd(); ".ics" : "text/calendar", }, ".ifb" : "text/calendar", (index < buffering events if // Stop 0) { return DEFAULT_MIME; req.removeListener("data", onData); ".iso" : "application/octet-stream", } : req, res, next) {req.removeListener("end", onEnd); the file directly ".jar" (err,"application/java-archive", handle: function buffers using var type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME; ile(filename, // function error: data) { Skip".java" on (err, "text/x-java-source", if (err) { return Fall through for missing files, thow error "; charset=utf-8" : type; // (/(text|javascript)/).test(type) ? type + for other problems ".jnlp" : "application/x-java-jnlp-file", err) { }, next(); ".jpeg" : "image/jpeg", if (err) { next(err); return; ".jpg" : "image/jpeg", if (err.errno === process.ENOENT) { return; TYPES : { ".3gp" : "video/3gpp", } ".js" : "application/javascript", next(); var url = Url.parse(req.url); ".a" Refire the buffered events // : "application/octet-stream", writeHead(200, { ".json" : "application/json", ".ai" : "application/postscript", ".log" : "text/plain", events.forEach(function (args) { ontent-Type": Mime.type(filename), ".aif" req.emit.apply(req, args); : "audio/x-aiff", ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff", var data.length, "audio/x-mpegurl", ".m3u" : ".aiff" : filename = Path.join(this.root, pathname); ".m4v" : "video/mp4", }); ast-Modified": stat.mtime.toUTCString(), ".asc" return; "application/pgp-signature", : / Cache in browser ".man" year "text/troff", for 1 : ".asf" : "video/x-ms-asf", ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 ".mathml" : "application/mathml+xml", ".asm" : "text/x-asm", filename += : "application/mbox", ".mbox" "index.html"; ".asx" : "video/x-ms-asf", end(data); } ".mdoc" : "text/troff", ".me" : "text/troff", ".atom" : "application/atom+xml", ".au" : "audio/basic", ".mid" : "audio/midi", Saturday, June 5, 2010 ".avi" : "video/x-msvideo",
  • 20. static.js // Buffer any events that fire while waiting "text/x-c", ".cc" : on the stat. var fs = require('fs'), : "video/x-flv",var events = []; ".flv" ".chm" : "application/vnd.ms-htmlhelp", Url = require('url'), "text/x-fortran", ".for" : function onData() { ".class" : "application/octet-stream", Path = require('path'); ".gem" events.push(["data"].concat(Array.prototype.slice.call(arguments))); : "application/octet-stream", ".com" : "application/x-msdownload", } ".gemspec" : "text/x-script.ruby", ".conf" : "text/plain", var lifetime = ".gif" 60: *"image/gif", function onEnd() { 1000 * 60; // 1 hour browser cache lifetime ".cpp" : "text/x-c", }; events.push(["end"].concat(Array.prototype.slice.call(arguments))); ".crt" : "application/x-x509-ca-cert", ".gz" : "application/x-gzip", var DEFAULT_MIME = 'application/octet-stream'; ".h" : "text/x-c", } ".css" : "text/css", // Mini mime module for static file serving req.addListener("data", onData); ".csv" : "text/csv", ".hh" : "text/x-c", module.exports ".htm"= { var Mime = { : "text/html", req.addListener("end", onEnd); ".cxx" : "text/x-c", ".html" : "text/html", ".deb" : "application/x-debian-package", (err); rn; setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) { ".ico" : { type: function fs.stat(filename, function { ".der" : "application/x-x509-ca-cert", var index = path.lastIndexOf(".");".diff" : "text/x-diff", this.root = this.root || process.cwd(); ".ics" : "text/calendar", }, ".ifb" : "text/calendar", (index < buffering events if // Stop 0) { ".djv" : "image/vnd.djvu", return DEFAULT_MIME; req.removeListener("data", onData); ".djvu" : "image/vnd.djvu", ".iso" : "application/octet-stream", } : req, res, next) {req.removeListener("end", onEnd); ".dll" : "application/x-msdownload", the file directly ".jar" (err,"application/java-archive", handle: function buffers using var type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME; // function error: data) { Skip".java" on (err, "text/x-java-source", ".dmg" : "application/octet-stream", ile(filename, return Fall through for missing files, thow error "; charset=utf-8" : type; if (err) { // (/(text|javascript)/).test(type) ? : "application/msword", ".jnlp" : "application/x-java-jnlp-file", ".doc" type + for other problems err) { }, next(); ".jpeg" : "image/jpeg", if (err) { ".dot" : "application/msword", next(err); return; ".jpg" : "image/jpeg", if (err.errno === process.ENOENT) { "application/xml-dtd", ".dtd" : return; TYPES : { ".3gp" : "video/3gpp", ".dvi" } ".js" : "application/javascript", next(); : "application/x-dvi", var url = Url.parse(req.url); ".a" Refire the buffered events : "application/java-archive", // : "application/octet-stream", ".ear" writeHead(200, { ".json" : "application/json", ".ai" : "application/postscript", ".log" : "text/plain", events.forEach(function (args) : "message/rfc822", ".eml" { ontent-Type": Mime.type(filename), ".aif" req.emit.apply(req, args); : "application/postscript", : "audio/x-aiff", ".eps" ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff", var data.length, "audio/x-mpegurl", ".m3u" : ".aiff" : filename = Path.join(this.root, pathname); ".m4v" : "video/mp4", }); ".exe" : "application/x-msdownload", ast-Modified": stat.mtime.toUTCString(), ".asc" return; "application/pgp-signature", : ".f" : "text/x-fortran", / Cache in browser ".man" year "text/troff", for 1 : ".asf" : "video/x-ms-asf", ".f77" : "text/x-fortran", ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 ".mathml" : "application/mathml+xml", ".asm" : "text/x-asm", ".f90" filename += : "application/mbox", ".mbox" "index.html"; : "text/x-fortran", ".asx" : "video/x-ms-asf", end(data); } ".mdoc" : "text/troff", ".me" : "text/troff", ".atom" : "application/atom+xml", ".au" : "audio/basic", ".mid" : "audio/midi", Saturday, June 5, 2010 ".avi" : "video/x-msvideo",
  • 21. static.js // Buffer any events that fire while waiting "text/x-c", ".cc" : on the stat. var fs = require('fs'), : "video/x-flv",var events = []; ".flv" ".chm" : "application/vnd.ms-htmlhelp", Url = require('url'), "text/x-fortran", ".for" : function onData() { ".class" : "application/octet-stream", Path = require('path'); ".gem" events.push(["data"].concat(Array.prototype.slice.call(arguments))); : "application/octet-stream", ".com" : "application/x-msdownload", } ".gemspec" : "text/x-script.ruby", ".conf" : "text/plain", var lifetime = ".gif" 60: *"image/gif", function onEnd() { 1000 * 60; // 1 hour browser cache lifetime ".cpp" : "text/x-c", }; events.push(["end"].concat(Array.prototype.slice.call(arguments))); ".crt" : "application/x-x509-ca-cert", ".gz" : "application/x-gzip", var DEFAULT_MIME = 'application/octet-stream'; ".h" : "text/x-c", } ".css" : "text/css", // Mini mime module for static file serving req.addListener("data", onData); ".csv" : "text/csv", ".hh" : "text/x-c", module.exports ".htm"= { var Mime = { : "text/html", req.addListener("end", onEnd); ".cxx" : "text/x-c", ".html" : "text/html", ".deb" : "application/x-debian-package", (err); rn; setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) { ".ico" : { type: function fs.stat(filename, function { ".der" : "application/x-x509-ca-cert", var index = path.lastIndexOf(".");".diff" : "text/x-diff", this.root = this.root || process.cwd(); ".ics" : "text/calendar", }, ".ifb" : "text/calendar", (index < buffering events if // Stop 0) { ".djv" : "image/vnd.djvu", return DEFAULT_MIME; req.removeListener("data", onData); ".djvu" : "image/vnd.djvu", ".iso" : "application/octet-stream", } req.removeListener("end", onEnd); ".dll" : "application/x-msdownload", the file directly ".jar"".bmp" req, "image/bmp", type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME; handle: function (err,"application/java-archive", using buffers : res, next) { : var // function error: data) { Skip".java" on (err, "text/x-java-source", ".bz2" : "application/x-bzip2", ".dmg" : "application/octet-stream", ile(filename, return (/(text|javascript)/).test(type) ? : "application/msword", : type; type + "; charset=utf-8" err) { if (err) { : "text/x-c", // Fall through for missing files, thow error for other problems ".jnlp" : "application/x-java-jnlp-file", ".c" ".doc" }, if (err) { ".dot" : "application/msword", next(err); next(); ".cab" ".jpeg" : "image/jpeg", : "application/vnd.ms-cab-compressed", return; ".jpg" : "image/jpeg", if (err.errno === process.ENOENT) { "application/xml-dtd", ".dtd" : return; TYPES : { ".3gp" : "video/3gpp", ".dvi" } ".js" : "application/javascript", next(); : "application/x-dvi", var url = Url.parse(req.url); ".a" Refire the buffered events : "application/java-archive", // : "application/octet-stream", ".ear" writeHead(200, { ".json" : "application/json", ".ai" : "application/postscript", ".log" : "text/plain", events.forEach(function (args) : "message/rfc822", ".eml" { ontent-Type": Mime.type(filename), ".aif" req.emit.apply(req, args); : "application/postscript", : "audio/x-aiff", ".eps" ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff", var data.length, "audio/x-mpegurl", ".m3u" : ".aiff" : filename = Path.join(this.root, pathname); ".m4v" : "video/mp4", }); ".exe" : "application/x-msdownload", ast-Modified": stat.mtime.toUTCString(), ".asc" return; "application/pgp-signature", : ".f" : "text/x-fortran", / Cache in browser ".man" year "text/troff", for 1 : ".asf" : "video/x-ms-asf", ".f77" : "text/x-fortran", ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 ".mathml" : "application/mathml+xml", ".asm" : "text/x-asm", ".f90" filename += : "application/mbox", ".mbox" "index.html"; : "text/x-fortran", ".asx" : "video/x-ms-asf", end(data); } ".mdoc" : "text/troff", ".me" : "text/troff", ".atom" : "application/atom+xml", ".au" : "audio/basic", ".mid" : "audio/midi", Saturday, June 5, 2010 ".avi" : "video/x-msvideo",
  • 22. Built-in Filter Modules • Authentication • Error Handler • Authorization • Gzip • Body Decoder • Log • Cache • Method Override • Conditional Get • Response Time • Debug • Session Saturday, June 5, 2010
  • 23. Built-in Data Providers • Static • Cache Manifest • Rest • Direct • Router • JSON-RPC • PubSub • More... Saturday, June 5, 2010
  • 24. Raphaël JS Raphaël is a small JavaScript library that should simplify your work with vector graphics on the web. Saturday, June 5, 2010
  • 25. Multi-Touch Vectors http://vimeo.com/11610421 Saturday, June 5, 2010
  • 26. multitouch-demo.js window.onload = function () { var R = Raphael(0, 0, "100%", "100%"), r = R.circle(100, 100, 50), g = R.circle(210, 100, 50), b = R.circle(320, 100, 50), p = R.circle(430, 100, 50); var start = function () { this.ox = this.attr("cx"); this.oy = this.attr("cy"); this.animate({r: 70, opacity: .25}, 500, ">"); }, move = function (dx, dy) { this.attr({cx: this.ox + dx, cy: this.oy + dy}); }, up = function () { this.animate({r: 50, opacity: .5}, 500, ">"); }; R.set(r, g, b, p).drag(move, start, up); }; Saturday, June 5, 2010
  • 27. Creating Shapes var R = Raphael(0, 0, "100%", "100%"), r = R.circle(100, 100, 50) .attr({fill: "hsb(0, 1, 1)"}), g = R.circle(210, 100, 50) .attr({fill: "hsb(.3, 1, 1)"}), b = R.circle(320, 100, 50) .attr({fill: "hsb(.6, 1, 1)"}), p = R.circle(430, 100, 50) .attr({fill: "hsb(.8, 1, 1)"}); Saturday, June 5, 2010
  • 28. Attaching Events function start() { this.ox = this.attr("cx"); this.oy = this.attr("cy"); this.animate({r: 70, opacity: .25}, 500, ">"); } function move(dx, dy) { this.attr({cx: this.ox + dx, cy: this.oy + dy}); } function up() { this.animate({r: 50, opacity: .5}, 500, ">"); } R.set(r, g, b, p).drag(move, start, up); Saturday, June 5, 2010
  • 29. Let’s combine them! Saturday, June 5, 2010
  • 30. • Serving static assets (HTML, CSS, JS) • Live Interaction (Pub Sub) • Performance Tweaks (Cache, Gzip) • Offline Mode (Cache Manifest) • HTTP Request Logging Saturday, June 5, 2010
  • 31. app.js (stack) require.paths.unshift("./lib"); var Connect = require('connect'); var root = __dirname + "/public"; module.exports = Connect.createServer([ {filter: "log"}, {filter: "body-decoder"}, {provider: "pubsub", route: "/stream", logic: Backend}, {filter: "conditional-get"}, {filter: "cache"}, {filter: "gzip"}, {provider: "cache-manifest", root: root}, {provider: "static", root: root} ]); Saturday, June 5, 2010
  • 32. app.js (Backend) var Backend = { subscribe: function (subscriber) { if (subscribers.indexOf(subscriber) < 0) { subscribers.push(subscriber); } }, unsubscribe: function (subscriber) { var pos = subscribers.indexOf(subscriber); if (pos >= 0) { subscribers.slice(pos); } }, publish: function (message, callback) { subscribers.forEach(function (subscriber) { subscriber.send(message); }); callback(); } }; Saturday, June 5, 2010
  • 33. index.html <!DOCTYPE html> <html lang="en" manifest="cache.manifest"> <head> <meta charset="utf-8" /> <meta name="apple-mobile-web-app-capable" content="yes"> <title>Node + Raphaël</title> <link rel="stylesheet" href="style.css" type="text/css" /> <script src="raphael.js"></script> <script src="client.js"></script> </head> <body> <div id="holder"></div> </body> </html> Saturday, June 5, 2010
  • 34. Demo Time! Saturday, June 5, 2010
  • 35. http://github.com/extjs/connect http://twitter.com/creationix http://raphaeljs.com http://nodejs.org Saturday, June 5, 2010
  • 36. Any Questions ? Saturday, June 5, 2010