Lessons from-a-rewrite-gotham

40
Lessons from a rewrite Rebecca Murphey • Gotham JS • New York, New York Saturday, July 9, 2011

description

 

Transcript of Lessons from-a-rewrite-gotham

Page 1: Lessons from-a-rewrite-gotham

Lessons from a rewriteRebecca Murphey • Gotham JS • New York, New York

Saturday, July 9, 2011

Page 2: Lessons from-a-rewrite-gotham

ere’s a subtle reason that programmers always want to throw away the code and start over. e reason is that they think the old code is a mess. ... e reason that they think the old code is a mess is because of a cardinal, fundamental law of programming: It’s harder to read code than to write it.

Joel Spolsky

Saturday, July 9, 2011

Page 3: Lessons from-a-rewrite-gotham

A content management system for the rapid creation of content-rich mobile applications.

A PhoneGap & Dojo system that consumes the output of the CMS to generate the application.

Saturday, July 9, 2011

Page 4: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 5: Lessons from-a-rewrite-gotham

Where a new system concept or new technology is used, one has to build a system to throw away, for even the best planning is not so omniscient as to get it right the "rst time. Hence plan to throw one away; you will, anyhow.

Fred Brooks

Saturday, July 9, 2011

Page 6: Lessons from-a-rewrite-gotham

Understand what you’re rewriting

Route

Data

Page Factory

The Router detects a change in the URL, looks for a matching route in toura.app.Routes, and parses any additional parameters out of the URL (such as the node ID, asset type, etc.).

If data is required for a route, such as node data or the user's favorites, the route requests the data prior to creating the page.

URL Change

The Route asks the page factory to generate a page controller. If the page controller is for a node, the Page Factory figures out which template (Audios, Images, etc.) the node uses. The page factory creates an instance of the page controller and hands the created instance back to the Route.

Router

Browser

Page Container

New Page Controller

Component

The Route asks toura.app.UI to place the Page Controller in the UI; in turn, toura.app.UI sets the content attribute of the Page Container. If there is already a page on screen, the Page Container handles the animation between the pages, and calls the destroy method of the old page, which results in the proper teardown of the old page and its components.

Page controllers are responsible for receiving data from the Page Factory. Once the page controller receives that data, it determines which components to display in its postMixInProperties method and sets up "placements," which define the components that will be placed on the page and the data that needs to be passed to them. The actual instantiation and placement of components is handled in the postCreate method of toura.pageControllers._Page, which is inherited by all Page Controllers.

Component

Component

Old Page Controller

Component

Component

Component

Page Controller

Component

Components are responsible for receiving and rendering data and reacting to user interaction.

Components whose content can change during a single page view should expose an API using setters (i.e. _setContentAttr) that allows Page Controllers to update their content.

Alternately, a component can define an attributeMap object that specifies how the component should react when a property is set.

Page Controller

Component

Component

Component

Component

Component

Once components are placed on the page, the Page Controller brokers communication between components.

Components announce events either by publishing a topic (for events that may have app-wide significance) or by calling a method (such as onClick). Page Controllers can subscribe to these topics or connect to these method calls (much like connecting to events). This happens in the Page Controller's postCreate method.

Every time the user goes to a new page, we check the age of the local data; if it is more than 8 hours old, we see whether we need to do an over-the-air update.

When the app is booted from a "cold" state (that is, it's not fast-app-switched), it goes through a bootstrapping process. This process ensures the application is working with the most recent data, and also sets up various application-wide functionality, including the Router, UI, Page Factory, Data Store, and several others.

Once these pieces are in place, the app triggers a URL change to the home node, and the process outlined here begins.

UI

Remote

Device Storage

Saturday, July 9, 2011

Page 7: Lessons from-a-rewrite-gotham

bootstrap()

Have we loaded the bundled data?

Populate DB with bundled data

Is the remote reachable?

no

yes

Get the remote version

dfd.resolve()no

Saturday, July 9, 2011

Page 8: Lessons from-a-rewrite-gotham

{ "name": "Audio Player", "audios": [{ "audio": { "_reference": "audio-23" }, "caption": { "_reference": "text-asset-64" } }], "parent": { "_reference": "node-365" }, "page_controller": "Audios1", "id": "node-369", "sharing_text": null, "sharing_url": null, "images": [{ "caption": { "_reference": "text-asset-60" }, "image": { "_reference": "image-174" } }, { "caption": { "_reference": "text-asset-61" }, "image": { "_reference": "image-182" } }, { "image": { "_reference": "image-180" } }, { "caption": { "_reference": "text-asset-62" }, "image": { "_reference": "image-181" } }, { "image": { "_reference": "image-179" } }, { "image": { "_reference": "image-178" } }, { "image": { "_reference": "image-177" } }, { "image": { "_reference": "image-176" } }, { "image": { "_reference": "image-175" } }, { "caption": { "_reference": "text-asset-63" }, "image": { "_reference": "image-183" } }], "type": "node", "children": [{ "_reference": "node-397" }, { "_reference": "node-398" }], "identifier": null}

Saturday, July 9, 2011

Page 9: Lessons from-a-rewrite-gotham

Identify pain pointsSaturday, July 9, 2011

Page 10: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 11: Lessons from-a-rewrite-gotham

/* {{^android}} */var mediaPath = "www/media/" + toura.pages.currentId + "/";/* {{/android}} *//* {{#android}} */var mediaPath = [Toura.getTouraPath(), toura.pages.currentId].join("/");/* {{/android}} */var imagesList = [], dimensionsList = [], namesList = [], thumbsList = [];var pos = -1, count = 0;/* {{#android}} */var pos = 0, count = 0;/* {{/android}} */

Saturday, July 9, 2011

Page 12: Lessons from-a-rewrite-gotham

toura.app.Has = function() { var device = toura.app.Config.get('device');

return { cssBackgroundContain : function() { return !( device.os === 'android' && device.version === '2-1' ); },

html5Player : function() { return device.os !== 'android'; },

iScrollZoom : function() { return device.os !== 'android'; } };};

Saturday, July 9, 2011

Page 13: Lessons from-a-rewrite-gotham

//>>excludeStart('production', kwArgs.production);if (toura.features.debugToolbar) { toura.app._Debug(); }//>>excludeEnd('production');

Saturday, July 9, 2011

Page 14: Lessons from-a-rewrite-gotham

Develop a communication manifesto

Saturday, July 9, 2011

Page 15: Lessons from-a-rewrite-gotham

Writing to be read means writing code ... with the idea that someone else will read it. is fact alone will make you edit and think of better ways to solve the problem you have at hand.

Stoyan Stefanov

Saturday, July 9, 2011

Page 16: Lessons from-a-rewrite-gotham

myComponent.set(key, val) to change state

myComponent.on<Evt>(data) to announce state changes

myComponent.connect(evt, handler) to listen for events & methods

myComponent.subscribe(topic) to react to published topics

dojo.publish(topic, data) to announce occurrences of app-wide interest

Saturday, July 9, 2011

Page 17: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 18: Lessons from-a-rewrite-gotham

Search Page Controller Application Data

Search Results Display

Search Input

toura.app.Data.search(term)searchInputInstance.onSearch(term)

searchResults.set('results', resultSet)return resultSet

Saturday, July 9, 2011

Page 19: Lessons from-a-rewrite-gotham

dojo.declare( 'toura.pageControllers.search.Search', [ toura.pageControllers._Page ], {

// ...

postCreate : function() { // ... this.connect(this.searchInput, 'onSearch', '_handleSearch'); },

_handleSearch : function(term) { if (term === this.lastSearchTerm) { return; } this.searchResults.set('results', toura.app.Data.search(term)); }, // ...});

Saturday, July 9, 2011

Page 20: Lessons from-a-rewrite-gotham

Sanify asynchronicity

Saturday, July 9, 2011

Page 21: Lessons from-a-rewrite-gotham

old & busted

images = toura.sqlite.getMedias(id, "image");

var onGetComplete = setInterval(function() { if (images.incomplete) return;

clearInterval(onGetComplete); showImagesHelper(images.objs, choice)}, 10);

new hotness

toura.app.Data.get(id, 'image').then(showImages, showImagesFail);

Saturday, July 9, 2011

Page 22: Lessons from-a-rewrite-gotham

var myAsyncThing = function() { var dfd = new dojo.Deferred(); setTimeout(function() { dfd.resolve('hello'); }, 1000); return dfd.promise;};

myAsyncThing().then(function(result){ console.log(result); });

Saturday, July 9, 2011

Page 23: Lessons from-a-rewrite-gotham

dojo.when(fn(), win, fail) react to maybe-asynchronous things, including promises

Saturday, July 9, 2011

Page 24: Lessons from-a-rewrite-gotham

Naming things is hardSaturday, July 9, 2011

Page 25: Lessons from-a-rewrite-gotham

ere are only two hard things in Computer Science: cache invalidation and naming things.

Phil Karlton

Saturday, July 9, 2011

Page 26: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 27: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 28: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 29: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 30: Lessons from-a-rewrite-gotham

yellowredblue

Saturday, July 9, 2011

Page 31: Lessons from-a-rewrite-gotham

Never write large apps

Saturday, July 9, 2011

Page 32: Lessons from-a-rewrite-gotham

e secret to building large apps is never build large apps. Break up your applications into small pieces. en, assemble those testable, bite-sized pieces into your big application.

Justin Meyer

Saturday, July 9, 2011

Page 33: Lessons from-a-rewrite-gotham

function nodeRoute(route, nodeId, pageState) { pageState = pageState || {};

var nodeModel = toura.app.Data.getModel(nodeId), page = toura.app.UI.getCurrentPage();

if (!page || !page.node || nodeId !== page.node.id) { page = toura.app.PageFactory.createPage('node', nodeModel);

page.init(pageState); toura.app.UI.showPage(page, nodeModel); } else { page.init(pageState); }

// record node pageview if it is node-only if (nodeId && !pageState.assetType) { dojo.publish('/node/view', [ route.hash ]); }

return true;}

Saturday, July 9, 2011

Page 34: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 35: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 36: Lessons from-a-rewrite-gotham

this.connect(this.videoList, 'onSelect', function(assetId) { var video = this._videoById(assetId); this.videoCaption.set('content', video.caption || ''); this.videoPlayer.play(assetId);});

Saturday, July 9, 2011

Page 37: Lessons from-a-rewrite-gotham

_setMediaIdAttr : function(mediaId) { var media = this.media = this.mediasCache[mediaId];

if (this.useHtml5Player && !this.player) { this._queuedMedia = media; return; }

this._queuedMedia = null;

if (this.player) { this.player.src = media.url; }},

videoPlayer.set('mediaId', mediaId);

Saturday, July 9, 2011

Page 38: Lessons from-a-rewrite-gotham

It takes con"dence to throw work away ... When people "rst start drawing, they’re often reluctant to redo parts that aren’t right ... they convince themselves that the drawing is not that bad, really — in fact, maybe they meant it to look that way.

Paul Graham

Saturday, July 9, 2011

Page 39: Lessons from-a-rewrite-gotham

Saturday, July 9, 2011

Page 40: Lessons from-a-rewrite-gotham

rebeccamurphey.com • blog.rebeccamurphey.com • @rmurphey

http://pinboard.in/u:rmurphey/t:lessons-from-a-rewrite/

http://spkr8.com/t/7930

Saturday, July 9, 2011