Last active
August 29, 2015 14:20
-
-
Save crabmusket/256b44514b0459fb2f8e to your computer and use it in GitHub Desktop.
Rendering a recursive tree of items with mercury
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
// Mercury is a 'truly modular frontend framework'. This helpful import gets | |
// us a lot of top-level symbols from submodules it re-exports. | |
var hg = require('mercury'); | |
// Like this one - h is a short constructor for HTML elements. | |
var h = require('mercury').h; | |
// And since we'll be running this in a browser using beefy, we need a reference | |
// to the document. | |
var document = require('global/document'); | |
// First, let's define the entire state of our application. We'll be making a | |
// big tree of Item objects, so our entire application state is simply the | |
// root of that tree. | |
function makeApp() { | |
return hg.state({ | |
root: makeItem(), | |
}); | |
} | |
// And to render the application, we just need to render the root. It will take | |
// care of rendering its children. | |
function renderApp(app) { | |
return renderItem(app.root); | |
}; | |
// Item is more complicated, a recursive structure that keeps a list of child | |
// Items. It also has a randomly-generated title string so we can distinguish | |
// visually between different Items. | |
function makeItem() { | |
return hg.state({ | |
// Elements of a state need to be constructed with one of the observ library | |
// types, value, struct or array. | |
title: hg.value(Math.random().toString(36).substr(3, 4)), | |
children: hg.array([]), | |
// Channels are like methods we can hook into events. In this case, the two | |
// events an Item responds to are adding a new child Item, and removing | |
// a particular child. These are both simple operations on the list of children. | |
channels: { | |
addChild: function (item) { | |
item.children.push(makeItem()); | |
}, | |
removeChild: function (item, i) { | |
item.children.splice(i, 1); | |
}, | |
}, | |
}); | |
} | |
// This is the trickiest part. When we render an Item that is a child of another | |
// Item, we must pass it a channel and identifier. This channel is used to send | |
// click events from the 'remove' button of the child Item to the parent. The | |
// parent provides a child with its removal channel, and an identifier so the | |
// parent knows which child has asked to be removed. | |
function renderItem(item, channel, i) { | |
// Every Item renders its title and a button to add children. | |
var contents = [ | |
item.title, | |
button('+', item.channels.addChild), | |
]; | |
// Note that when rendering the root window in the app, we don't pass a channel | |
// or index. So no removal button is rendered. | |
if (channel) { | |
contents.push(button('x', channel, i)); | |
} | |
// And finally we can add in all the rendered child Items. Note how we pass | |
// a reference to the removal channel of the current Item, and the index of | |
// the child Item in the parent's list. | |
contents = contents.concat(item.children.map(function(child, i) { | |
return renderItem(child, item.channels.removeChild, i); | |
})); | |
return h('div.window', contents); | |
}; | |
// Convenience function to make a button input. When clicked, it sends 'value' | |
// to 'channel'. | |
function button(text, channel, value) { | |
return h('input', { | |
type: 'button', | |
value: text, | |
'ev-click': hg.send(channel, value), | |
}); | |
} | |
// Finally, tie that all together by rendering the application. | |
hg.app(document.body, makeApp(), renderApp); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The finished product, with some CSS applied: