-
Notifications
You must be signed in to change notification settings - Fork 0
No Widget Is An Island
This is an easy mission with profound implications. You simply have to navigate a tree of Matrix objects, or models, from one provided node to find a clearly specified other target node, and return it. The provided code handles the rest.
What makes this easy is that all models (other than the root node) know their parent, and of course they know their children, an ordered list, if any.
Yes, you can get there from here.
We even provide a utility fget that searches such a tree, and that utility takes parameters you can use to control where it looks. Simpler utilties let you navigate more, well, simply.
We even provide a visualization of the tree to help you think about the navigation of the state tree behind it.
What makes this profound is that:
- a Matrix application is largely one such tree, comprising all interesting application state; and
- every model property formula is passed the model as its sole parameter; and
- every event handler can derive its owning model.
When writing formulas or event handlers, the world is our oyster.
Someone asked about multiple Matrixes in one app, so we quickly implemented that. See the "Multi MX" faux debugger code.
Simply learn to navigate the Matrix tree of models, using provided utilities or writing your own.
ReactJS introduced the world to declarative GUIs, yay, but tightly constrained the information available to a component. Not so yay.
The simple fact is that powerful GUIs mean sharing state, because different widgets often reflect the same state; e.g., a "delete" button will be disabled or not even visible if nothing to delete has been selected. This was the first thing we wanted to do with ReactJS and it did not go well because, in our design, the garbage can delete icon was in a toolbar, not sitting on the ToDo item as specified in the TodoMVC spec.
React "props" were great for customizing reusable widgets, but not for passing around changing information as the user worked. They tried passing around callbacks so widgets could work together, and conceived but discouraged context, but it was all awkward, boilerplate-y and brittle in the face of refactoring.
Enter Flux with global stores of state acted on by a limited number of actions and with derived state computed in a strictly uni-directional flow. Flux led to Redux, Vuex, and others. That worked and was a prima facie good way of dividing and conquering GUI app complexity, but was cumbersome and verbose. So now they have Redux Toolkit and hooks and have let context out of jail.
Matrix took a different approach from the beginning.
Matrix embraces the idea that complex applications are deeply interdependent, especially GUI applications. Matrix allows derived state to use and act on arbitrary other state without restriction.
Well, cycles throw an exception, but we could work around that, if needed. We just have not needed it yet.
While global sounds dangerous, in fact Matrix formulas and handlers must navigate inside out from the local model to the Matrix around it to achieve global reach, so we benefit from a natural inside-out scoping of the application itself.
In the "Hello, Cells" intro, we deliberately stuck to one property of one widget, so no navigation to other widgets was needed. In this mission, we learn to navigate from a given "me" or "self" to other models in scoped, controlled fashion so we reliably connect with the right other model.
First, run the app and examine the actual display; it may have changed. But it should look much like this:

For each colored square "toggler" button, you will see a tree element with a border of the same color. The label and place in the visual hierarchy will suggest how to navigate the data tree that guided the construction of the visual hierarchy.
To accomplish this Mission, supply a function that will receive a starting node "me", navigate to its matching bordered tree node, and return it.
Hint: The button labelled "me" will be straighforward to solve. We provide that solution, and the solution to a worst-case navigation.
Some direct navigators you might use:
| Navigator | Behavior |
|---|---|
| (mx-par X) | returns the parent of X |
| (kids X) | returns the children of X |
| (prevsib X) | returns the sibling preceding X in their parent's list of kids |
| (nextsib X) | returns the sibling following X in their parent's list of kids |
Matrix also provides a tree search utility called fget, f for "family":
| function | fget |
|---|---|
| Syntax | (fget what where options*) |
| Example | (fget :trashcan :me? false :up? true :inside? false) |
Required inputs:
| Parameter | Mnemonic | Description |
|---|---|---|
| what | "what is sought?" | the name or type of the sought node, or a test function |
| where | "where do we look?" | the starting node of the search |
Options with their defaults:
| Option | Default | Description |
|---|---|---|
| :me? | false | should we consider the start node? |
| :inside? | false | should we search descendants of the starting node? |
| :up? | true | should we search recursively up to ancesters, starting with parent of "where"? |
| :wocd? | true | should we NOT form reactive dependency on the "kids" nodes as wesearch them? |
Some handy macros/fns that leverage fget:
(fmu :x) => search up for :x from captured 'me biding: (fget :x me :me? false :inside? false)
Help learning mxWeb is available on the #matrix channel of Clojurians Slack.
Issues with this trainer can be reported on its GitHub repo.