While I’m not a big fan of form-based interfaces, it’s sometimes the best way to get basic object information into a system. Once there, manipulating objects via a more intuitive UX is the target. But forms live on in systems as a convenient, time-tested way to capture user input.
Meteor puts a different twist on the way interfaces are built - reactivity is what’s at the heart of Meteor interfaces. Instead of getting the data and building the interface from it, the interface is built from reactive templates that re-render when the data they depend on changes. This allows a developer to simply set things up and let them work rather than writing code to check for changes and update the interface. Quite a significant difference to the art of building web applications!
What’s in a Form?
A form is pretty simple. It’s a set of editable controls with a save or cancel button, usually at the bottom. The controls can be fairly sophisticated, but generally they’re not. The sophisticated ones have dependencies between the values being collected so that changing one might somehow change another. There are also validations that can be set up to make sure only acceptable values can be entered. Between validation and dependency, I can make sure forms collect ‘legal’ and ‘consistent’ values.
But these are bigger fish - I’m going to focus on simply getting information and saving it. I’m going to use Bootstrap3 and Coffeescript in the examples to follow.
I’ll create form that takes the logged in user’s identity as their first and last name and store it into the database.
The form page is simple enough. It starts with a template wrapper (a1) that is accessed by the router, and calls out (a3) to the identity form template (a7). The form pulls uses the person (a8) pulled from the coffeescript (a44) to display the person’s first name (a14) and last name (a18). At the end of the form, the form calls out (a21) to the update buttons template (a25).
The update buttons template can potentially be used by many forms, so it’s separated out. Basically, if the any form item has changed (a26) a Save button (a29) and a Cancel button (a30) are shown. If no form item has changed, they stay hidden.
The common code is separated out like the common interface. Here there is some event management code (a37) that is typically used by event handlers, set up in a namespace (a35) to keep everything neat and tidy.
The update button management is found here, too. When the update buttons are created (a41) whenever the form renders, the change state is cleared and stored in the Session. This value is read from the Session (a43) and used during rendering the buttons (a26).
The code that handles the identity form is simple. The person that the form is targeting (a8) is determined by a reactive database search (a45), or given by empty values (a46) the first time around.
The rest of the code is event handling. A click on the Save button (a29) triggers the submit event on the form (a50) which pulls the first name (a52) and last name (a53) from the form and calls the server (a54) to set the value in the database. Changes are then reset (a55) to hide the update buttons (a55).
Clicking the Cancel button (a30) means all changed values should be discarded and reset to the original values. This is done by capturing the form (a59) removing its child elements (a60) and re-rendering it (a61). Changes are also reset (a62) to hide the update buttons (a55).
Finally, when a key is pressed on a form item, or the value of the form item is changed, the event handler (a64) is triggered and marks the form as changed (a65) to display the update buttons (a55).
None of this could be done without the people collection in the database. I declare it (a66) in a shared file used by the client and the server.
When I want to save the person into the database (a54) it’s the method on the server (a69) that gets called. It’s Mongo underneath the covers here, and I form a selector (a71) and modifier (a73) to pass to the upsert command (a77) which will create or update the specified record.
Finally, IronRouter is used to declare routes for the application. The identity route is set up to display the identity page (a80).
Of course, I’ve left out a few parts of the app, but this demonstrates the meat of the code fairly accurately. The big idea is that everything is driven by reactivity and user interaction.
-
When the person is pushed to the client from the server, the values are rendered into the form.
-
When items on the form have changed, the client-local reactivity affect the visibility of the update buttons.
Relieving the developer of these issues makes programming much simpler. Submitting changes to the server on commital or rerendering the form with original values on cancellation is an easy way to think about what’s going on. It’s all very close to the page, I’m thinking in terms of rendering templates and making simple responses to events, instead of being concerned with managing elements through a higher level interface.
(Note that there are high level form abstractions for Meteor like Dobbertin’s Autoform or just form validations like Copley’s Mesosphere. However, these require some level of detailing the mongo collection schema which has less to do with reactivity and more to do with building manageable systems, so they’ll just get a mention here.)
Dropping Autopublish
When developing a project in Meteor, the autopublish package is part of the app by default. This synchronizes the database between the client and the server. The benefit of this is that developers can concentrate on the logic of event handling and display rather than considering the transmission of which database entries are accessible.
At some point, a switch to the publish/subscribe model should be made and the autopublish package should be dropped. So when that bold step is taken:
all the data in the app disappears. To get it back, the client must subscribe to the database values it cares about, and the server must publish those values to the client.
Fortunately, this is easy.
In the router, I create a subscription (b4) to ‘identity’ that effectively states the the identity route needs the data pushed by the identity publisher on the server (b5).
Now, instead of pushing all the entities in the people database to the client, only the one with the matching user id will be pushed.
And since I’m about to deal with a little more data, this will be important.
Adding Location
Now I’ll do something that get’s a little tricky to do reactively.
I want to add a Country/State/City selector that changes reactively - that is, one that’s a little bit sophisticated. The state depends on the country and the city depends on the state. Note also that it’s not really called Country/State/City in the interface - it might be Country/Province/City if Canada is selected, for instance. But I’m talking about three levels of cascaded location.
Additionally, I want to use Moreto’s Bootstrap-Select package so I get nice looking pulldowns that match the rest of the bootstrap interface. This may seem innocent, but it forces a whole extra level of interface control that strain’s the reactive mechanisms considerably.
I introduce the three new select elements - one for countries (c40), one for states(c52), and one for cities (c64). The options for these selects are in their own templates(c46, c58, c70) - so each set of options can be rendered separately from its corresponsing select.
When considering the code that drives the selects, things get more complicated.
First the triggers. Responding to events drives the cascade process. When a change item changes (c101) the form item change is set to turn on the update buttons. When a new country is chosen (c104), we make the state change. When a new state is chosen (c107), we make the city change. Finally, a subscription to locations is created to start the cascade when ithey are published.
Next the changers. Changing the level in the cascade above has to subscribe to a different location and trigger user interfaces updates for the selectors. To change the state (c138) I subscribe to the selected country and update the state selector when the states for that country are published (c140). To change the city (c166) I subscribe to the selected country and state and update the city selector when the cities for that country and state are published (c169).
Now the user interface updaters. When the user interface for the selectors is updated, the options in a particular select are replaced by rerendering option templates. To change the country select (c114) a check is made to see if it’s in the DOM yet (c116) and if so, any existing options are removed (c119) and the country select options are rendered and inserted into the select (c120). Getting the country options is done in a helper (c123) that finds the countries that are available (c124) and returns them in an array (c126). To change the state select (c142) any existing options are removed (c146) and the state select options are rendered and inserted into the select (c147). Getting the state options is done in a helper (c123) that finds the states that are available for the chosen country (c152) and returns them in an array (c154). To change the city select (c172) any existing options are removed (c176) and the city select options are rendered and inserted into the select (c177). Getting the city options is done in a helper (c180) that finds the cities that are avialable for the chosen country and state (c183) and returns them in an array (c185).
Finally, the resonders to rendering. This last bit is what sycronizes the bootstrap-selects to the selects through the manipulation of selectpickers. for countries, states and cities, selectpickers are initialized (c132, c160, c191), refreshed (c133, c161, c192) and the person’s country, state or city is selected (c134, c162, c193). Then for the country and state, we fired the cascade for the next level down (c136, c164). Care is taken to preserve change due to initialization versus change due to user interaction.
Admitedly, this code is more than a bit repetitive and should be refactored, but I’m trying to make sure it’s clear what’s going on. Note that the code is reactive - the response to changes is updated subscriptions that cause new rendering, not direct manipulation of the user interface.
The data itself for the country, states and cities is stored in what I’ve traditionally called lookup tables. The idea is that I don’t want to hard code it because it might rarely change, but I’m not going to change the values within the course of running this system. They’re values that can be reliably looked up.
When I publish locations, I’m really just publishing a few lookup table entries. Note that if country or state isn’t specified, then the corresponding keys won’t be found (c111, c140, c170). This is all designed to pull the correct values when required.
Finally, like all good Meteor programs, the existence of all needed data on the server is asserted when you start. In this case, the lookup table needs to include a list of countries, a list of states for each country, and a list of each city for each country-state combination. In this case I’m just including the United States, its fifty states, and cities with populations over 8000 people. I left off a lot of data, but you get the idea.
Thoughts
Cascading data turns out to be a non-trivial bit of code when it needs to work with specialized components in a chosen interface framework (in this case Bootstrap-select in Bootstrap). It’s clearly doable, but it feels like more work than it should be. In fact, the chain of “triggered change of subscriptions to updates and interface cleanup” has pretty much become a reactive pattern for me when using components that rely on unobtrusive javascript and setup calls to implement sophisticated interactions.
Doing forms reactively is certainly a different approach than what I did before using Meteor, but it does keep methods short and simple despite the number of moving parts. But once you’re done with them and the data has been entered into the system, you can use the kind of social reactivity that Meteor was made to shine for.