Wiki items can be moved about within and between pages. We use jquery drag and drop handling which we set up when refresh builds pages. A move sends an action to the server and then logs it to the journal once the client knows this operation has been completed on the server.
# Drag
We configure and then listen for sortupdate events. From these we infer what action the user intended.
After emitting the elements of a page refresh sets up jquery drag handling in the initDragging function.
initDragging = ($page) -> options = connectWith: '.page .story' placeholder: 'item-placeholder' forcePlaceholderSize: true $story = $page.find('.story') $story.sortable(options) .on('sortupdate', handleDragging)
The user's manipulations return to us as calls on handleDragging. Multiple calls originate from a single drag and drop motion. Handling logic infers from these which form of edit we have seen: moving an item within a wiki page, a move action, or moving an item between two wiki pages, a remove from one and add to the other.
# Action
We encode page editing operations into actions. These are objects in the style of command pattern that describe the intended operation in a form that can be transmitted to the server and saved for later reconstruction of the page revision history.
An item moved within a single wiki page creates a 'move' action. This includes an ordered list of item ids at the completion of the move.
{type: 'move', order: order}
An item moved between two wiki pages creates a 'remove' from one page and and an 'add' to the other.
{type: 'remove'}
The 'add' action includes the item to be added as well as details about where it is to appear.
{type: 'add', item, after: before?.id}
To each of these we add the item's id and then pass further handling to the server through the pageHandler.
action.id = item.id pageHandler.put $thisPage, action
# Put
The pageHandler makes the ajax call that updates the server's copy of a page along with pre and post call updates to client state.
In advance of the ajax put logic handles both explicit and implicit forks. The fork button is explicit but any other editing operation on a remote page implicitly forks the page before update. Put logic also decides if the write should be to the origin server or the browser's local storage.
# Journal
After a successful ajax call the put logic updates the journal in the client copies of the page. Slightly different approaches are applied to the dom-resident copy and the model resident copy.
The dom-resident page dates back to our earliest jquery centric implementation. In this version jquery modifies the page first, launching the ajax update, and then the dom-resident journal is updated to complete the transaction.
The model-resident page is unchanged until the ajax call completes. Then the action is applied to this copy following incremental update logic in the revision module duplicating the action interpretation of the server.
switch action.type when 'create' if action.item? page.title = action.item.title if action.item.title? page.story = action.item.story.slice() if action.item.story? when 'add' add action.after, action.item when 'edit' if (index = order().indexOf action.id) != -1 page.story.splice(index,1,action.item) else page.story.push action.item when 'move' index = action.order.indexOf action.id after = action.order[index-1] item = page.story[order().indexOf action.id] remove() add after, item when 'remove' remove()
# Reflection
We send Actions to the server rather than whole revised pages which some see as a departure from REST api design. Authors can type ahead of server request completions. The implicit fork and the paragraph split logic is careful to only emit one Action at a time so as to not overrun the server.
Modern single-page application design discourages by-hand management of dom updates. Better to revise a model and let a diff-algorithm update the dom. Our jQuery based drag animation led us to incrementally finishing its work instead of starting over with a model update.