Skip to content

Instantly share code, notes, and snippets.

@crabmusket
Last active Aug 29, 2015
Embed
What would you like to do?
Rendering a recursive tree of items with mercury
'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);
@crabmusket
Copy link
Author

crabmusket commented May 8, 2015

The finished product, with some CSS applied:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment