In the closing of my What is MetaZen? article I promised I'd share some of our progress on MetaZen, so this week I'm going to tie in some of the last few weeks of Reactive by Example with a MetaZen proof of concept.

This is not yet a fully functioning proof of concept, but there's enough finished that I don't expect too many changes, and my thought is that if I make the changes in public via this blog then anyone using MetaZen in the future will have something that shows my thought process on designing the system.

Note: After writing this article, it was obvious that "I don't expect too many changes" was horribly premature because only a week or two later I re-wrote a lot of this.

Taking our previous example of an HTML page with a fullName display and a firstName and lastName input field, one could write the same page as a template (drawing from Aurelia's use of templates, but with our own twist).

<template>
  <div>
    <(fullName)/>
  </div>
  <div>
    <label for="firstName">First Name:</label>
    <input name="firstName" type="text" value=(firstName)>
    <label for="lastName">Last Name:</label>
    <input name="lastName" type="text" value=(lastName)>
  </div>
</template>

The big difference here is that attributes that use () instead of "" to set an attribute are treated as Observable values.

The same goes with HTML elements such as <(fullName)/> which indicates that the element value is determined by an Observable<HTMLElement>.

You can compare this with Aurelia and the .bind attribute modifier as well as the ES6 style ${expression} within templates. Aurelia does some behind-the-scenes magic to make the expressions reactive, but it doesn't always work without some additional code in the view model.

We don't have to worry about that here because we will handle it appropriately in our code generator, and for now we'll just assume that problem has been addressed. We know we can implement it using ReactiveX without requiring us to use Aurelia or Angular etc.

Now, for the code that does the work:

(def IPersonDetailViewModel
  (protocol
    (method fullName:string [])))

(def PersonDetailViewModel
  (type
    (properties [
      model:PersonModel
      firstName:string
      lastName:string
    ])))

(impl PersonDetailViewModel
      IPersonDetailViewModel
        (method fullName:string []
          (join firstName lastName " ")))

(def PersonModel
  (record [
    firstName:string
    lastName:string
  ]))

An important thing to remember is that even though this looks like a programming language, it's not exactly. It's meta data.

It doesn't have to have all of the unimportant details. As long as it correctly describes what we're trying to do, we can worry about the details later because we can programmatically manipulate it if necessary, and we can fill in the implementation details when we write the code generators.

With that said, breaking this down into smaller chunks is probably necessary since most people don't know ZScript.

We start with (def PersonModel), which means define a symbol named "PersonModel"

(def PersonModel
  (record [
    firstName:string
    lastName:string
  ]))

This defines PersonModel to be a record.

Drawing inspiration from old C struct (before you could add methods) and Clojure's record, a record within ZScript is a special kind of type that only has properties. This makes it less verbose to declare because of the assumed properties construct.

Compare that to the type definition for the PersonDetailViewModel.

(def PersonDetailViewModel
  (type
    (properties [
      model:PersonModel
      firstName:string
      lastName:string
    ])))

This defines a type (class for those of you familiar with most object oriented programming languages). This type has three properties. The first, model, is a property of type PropertyModel, and the next two, firstName and lastName, are properties of type string.

Alas, for now this too could have been a record, except for the small piece of code that followed it, which could not have been done on a record.

(impl PersonDetailViewModel
      IPersonDetailViewModel
        (method fullName:string []
          (join firstName lastName " ")))

This piece of code says that PersonDetailViewModel implements the IPersonDetailViewModel protocol (aka interface, or trait) and provides the implementation, which in this case is just one method called fullName which returns a string and doesn't take any arguments ([] is an empty list of arguments). The implementation uses a string join function to join two strings, separating them with a space.

And then back to the top, we see the declaration of that protocol:

(def IPersonDetailViewModel
  (protocol
    (method fullName:string [])))

Gaps

Now, even with that full explanation of the code, if you've followed along so far then you'll notice that we have some obvious gaps:

  • How does the model get presented to the view model?
  • How does the view model get updated when it looks like the binding is via value and oninput event is never handled.

One way is to ignore the problem for now. We could hard-code the code generator and make assumptions about the code, such as:

Types that end in ViewModel automatically expose any properties of the model that isn't explicitly implemented and that are referenced by the View.

and

Any <input> element that references a writable property for the value should be wired as a two-way binding.

At this point this solution doesn't feel right, since it requires some fairly rigid coding standards including naming conventions.

Another way to close those gaps might be to change the PersonDetailViewModel to be a little more specific.

(def PersonDetailViewModel
    (type
      (properties [
        model:PersonModel
      ])
    (methods [
      firstName:string [] (.firstName model)
      lastName:string [] (.lastName model)
    ]))

You'll notice that now firstName and lastName are now methods that return those values from the model. Should this logic be here in the view model or should we move it to a separate Presenter? I'm not sure yet, so we'll leave that one for later.

Next, lets solve the other problem of handling oninput.

Lets start by defining a new protocol (aka interface or trait depending on the programming language).

(def IOnInputHandler
    (protocol
      (method oninput:(fn [event:InputEvent]) [field:string])))

This new protocol declares a method that takes a string argument and returns a function that takes an InputEvent argument.

Now, for the PersonDetailViewModel implementation of that protocol.

(impl PersonDetailViewModel
      IOnInputHandler
        (method oninput [field:string]
          (fn [event:InputEvent]
            (.next (field) event.target.value))))

This implementation returns a function that when invoked it gets the event.target.value from the event and uses that as an argument to pass to the .next function on the specified field.

.next is how you assign a new value to a Subject.

So, now that our view model has an oninput event handler that can be used for each property, the necessary changes to the HTML are as follows:

<label for="firstName">First Name:</label>
<input name="firstName" type="text" value=(firstName) oninput=(oninput firstName)>
<label for="lastName">Last Name:</label>
<input name="lastName" type="text" value=(lastName) oninput=(oninput lastName)>

So now the two input elements have oninput event handlers.

You might notice the oddity where we're passing firstName and lastName to oninput, but that function takes a string, so shouldn't we be passing a string value instead of a method?

In ZScript a string is never really a string. It's generally a Subject<string> unless it's read-only, and then it's a Observable<string>.

So, following through the logic of the oninput handler, (oninput firstName) passes firstName to the view model's oninput method. This returns a function that is essentially (.next firstName event.target.value). You'll notice that (field) is a method invocation, which was necessary because firstName is a method now (where previously it was a property).

Now, when the user types something into the input boxes, the correct property's next function is called to update that property.

To loop back around, lets see how fullName DOM element is populated. If you don't remember, the HTML looks like this:

<div>
  <(fullName)/>
</div>

The special HTML element <()/> sets up a subscription to an observable to populate the DOM. In this case it's calling the method or property fullName to populate that DOM element.

fullName was defined as such:

(method fullName:string []
  (join firstName lastName " ")))

What's not obvious at first is this method isn't just returning a string, but rather it's returning an Observable<string> as explained earlier.

Also, ZScript automatically implements the combineLatest functionality when you use more than one Observable as arguments to a function call, so join here gets re-evaluated any time firstName or lastName get modified, and then that causes fullName to emit a new event, which in turn updates the HTML DOM Element with the updated string.

In the end, this is quite a bit more code than the previous example, but it's Clean and uses a pretty good design, with the view being implemented entirely in HTML with only minimal ZScript embedded code, and we have separated our view model from our model.

We still need to decide if the view / view model design is good enough, or if we need to have a separate presenter, but for now I think this is good enough. Note: Although the answer isn't obvious in this scenario, the presenter will eventually need to be separated in the final full stack app because the model will likely only exist on the server and we'll need a separate presenter class to send the view model to the client.

Review

Now, one last look at the final code in all of its glory:

<template>
  <div>
    <(fullName)/>
  </div>
  <div>
    <label for="firstName">First Name:</label>
    <input name="firstName" type="text" value=(firstName) oninput=(oninput firstName)>
    <label for="lastName">Last Name:</label>
    <input name="lastName" type="text" value=(lastName) oninput=(oninput lastName)>
  </div>
</template>
(def IPersonDetailViewModel
  (protocol
    (method fullName:string [])))

(def IOnInputHandler
  (protocol
    (method oninput:(fn [event:InputEvent]) [field:string])))

(def PersonDetailViewModel
    (type
      (properties [
        model:PersonModel
      ])
      (methods [
        firstName:string [] (.firstName model)
        lastName:string [] (.lastName model)
      ]))

(impl PersonDetailViewModel
      IPersonDetailViewModel
        (method fullName:string []
          (join firstName lastName " ")))

(impl PersonDetailViewModel
      IOnInputHandler
        (method oninput [field:string]
          (fn [event:InputEvent]
            (.next (field) event.target.value))))

(def PersonModel
  (record [
    firstName:string
    lastName:string
  ]))

More Gaps

We have a bit more work to do to turn this into a full single page application, but it's a great start.

One of the things I don't like about this is the tight coupling with HTML. What if we wanted to make an application that uses wxWidgets or QT or whatever? It's bad enough that HTML isn't abstracted, but the oninput event handler implementation is very rigid.

I left it in for a couple of reasons.

  • I don't think that will be terribly hard to address after the fact; after all, all of this is only data, and we could fairly easily query for the places where we use these event handlers and programmatically manipulate the code to do the right thing, or we could simply figure out the correct way to handle it within the code generator.
  • Since we don't know enough yet, this is a place where we can iterate on it later. We just need to keep that in mind and not rely too much on this approach until a better solution presents itself.

I could have just as easily made something up, but nothing really graceful presented itself, so I'll wait until I have a better idea. In the mean time, it serves its purpose and gets the point across since HTML is a fairly well known standard.

Meta Programming

It's not obvious, yet, as to how this is actually meta programming, but it will become more obvious in the future when we start showing the tools that let us manipulate the ZScript data and HTML templates, and it will become even more apparent when we use this meta data to generate our database models, services, and client applications.

Feedback

Do you like it so far?

Feel free to e-mail me at trichards at indiezen.com. I would love to read any comments / suggestions you may have. If enough people send me an e-mail then I'll set up some forums.

Happy coding!