Refresh will fetch a page and use it to fill a dom element that has been ready made to hold it. Rendering includes fetching and running the necessary plugins.
Aside: The client-side code is organized as a collection of modules that exist as coffeescript in lib. Grunt compiles these and then builds client.js with browserify. Here we refer to lib modules as if they are close friends.
The server fills its initial (and only) html page with empty divs, one for each wiki page to appear in the lineup. The last few lines of legacy will boot up the refresh cycle for each wiki page on jquery dom ready.
$ -> state.first() $('.page').each refresh.cycle active.set($('.page').last())
Try adding debugger to this handler and step briefly into each of these calls. This will be easier if you add a breakpoint to refresh.cycle rather than stepping through the jquery loop.
State manages the browser's state used by the back button. Active highlights an active page and works the side-scrolling in response to arrow keys. Refresh does the heavy lifting fetching and rendering json into the dom.
# Get
The refresh cycle begins by fetching the wiki page json from any of a number of locations. We provide the pageHandler an object with various facts about the desired page and a couple of async handlers for successful and unsuccessful gets.
pageHandler.get whenGotten: whenGotten whenNotGotten: createGhostPage pageInformation: pageInformation
If the pageHandler fails we create a ghost page that offers to create the missing page and post it to the server.
Try setting a breakpoint here and then click a link to a non-existent page. Here is what you will see.
We title the ghost page based on what we can find of the link that begot it. If this fails, we use the slug as title. Yuck.
We rummage through the sitemaps of our neighborhood to see if the page might exist there. PageHandler follows some strict rules for its search. Here we can be more generous because we're talking to the user.
We construct the ghost page with all this advice and one unusual core plugin, future. Future makes the offer to create the desired page. So far we have just made the page that makes this offer.
See also Where Pages Live
# Build
Whether gotten or constructed, we now have page content to be rendered into the dom. The gotten path adds the extra step of registering new neighbors, sites mentioned in the page json.
Aside: Pages variously exist as json strings, the objects constructed from those strings, and more recently, the pageObjects that bundle pages with site details about their source. The longish name reminds us that this is the preferred representation while some points in the codebase still handle unbundled pages.
The refresh module exposes two additional build entry points besides cycle where we started. We travel through these, marking the page div in various ways, before getting into serious building which starts by discarding anything that might be left in $page.
$page.empty()
We prefer the convention of naming variables that hold dom elements, usually jquery wrappers, with dollar signs. Thus $page is a handle on the dom structure of the page.
Try finding calls to buildPage and rebuildPage in other lib modules. Expect to find event handlers of various kinds.
Building proceeds to add a layer of divs under $page that will be filled in and linked up with a series of emit-something calls.
$twins, $header, $story, $journal, $footer
We often register event handlers on dom objects as they are built using calls on init-something functions. We now prefer jquery's event delegation mechanism and have seen delegates collect in the one-time initialization of the legacy module, hopefully not their final resting place.
# Story
Rendering the page's story is made complicated by the async calls needed to load plugins and possible async calls they might make in order complete their rendering. We delegate iterating over story items and provide a callback that does the actual work.
pageObject.seqItems (item, done) -> $item = $ """<div class="item #{item.type}" ... $story.append $item plugin.do $item, item, done
The plugin module handles loading and caching plugin javascript from the origin server. It creates a reasonable substitute when a plugin doesn't exist or fails to emit successfully.
A plugin is expected to emit dom elements before binding to others. We intend for this sequence to be asynchronous with all emits completed before binds are started. Plugins that can't emit synchronously must provide a callback as the third argument. We respect this protocol but don't yet separate emits and binds.
Try locating this logic inside the plugin module.
if script.emit.length > 2 script.emit div, item, -> script.bind div, item done() else script.emit div, item script.bind div, item done()
# Reflection
Wiki finds and renders pages from idiomatic json as one step of hypertext browsing. We defer just how linking works for a subsequent hack. Rendering leaves many clues in both the dom and model object to guide subsequent event handlers.
Wiki's page rendering logic establishes a contract with plugins that allows them to work within the dom so as to interact with the user and other plugins by convention more than by strict pathways defined in wiki's core.
As some of the oldest and most frequently refactored code one can see in refresh the work of many hands in many styles. For now be content to know what is here. Our approach to refactoring will be left for a later hack.