Lessons from-a-rewrite-gotham
-
Upload
rebecca-murphey -
Category
Technology
-
view
2.474 -
download
0
description
Transcript of Lessons from-a-rewrite-gotham
Lessons from a rewriteRebecca Murphey • Gotham JS • New York, New York
Saturday, July 9, 2011
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
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
Saturday, July 9, 2011
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
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
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
{ "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
Identify pain pointsSaturday, July 9, 2011
Saturday, July 9, 2011
/* {{^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
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
//>>excludeStart('production', kwArgs.production);if (toura.features.debugToolbar) { toura.app._Debug(); }//>>excludeEnd('production');
Saturday, July 9, 2011
Develop a communication manifesto
Saturday, July 9, 2011
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
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
Saturday, July 9, 2011
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
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
Sanify asynchronicity
Saturday, July 9, 2011
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
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
dojo.when(fn(), win, fail) react to maybe-asynchronous things, including promises
Saturday, July 9, 2011
Naming things is hardSaturday, July 9, 2011
ere are only two hard things in Computer Science: cache invalidation and naming things.
Phil Karlton
Saturday, July 9, 2011
Saturday, July 9, 2011
Saturday, July 9, 2011
Saturday, July 9, 2011
Saturday, July 9, 2011
yellowredblue
Saturday, July 9, 2011
Never write large apps
Saturday, July 9, 2011
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
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
Saturday, July 9, 2011
Saturday, July 9, 2011
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
_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
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
Saturday, July 9, 2011
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