In a previous article, Thoughts on MetaZen, I showed a simple proof of concept web page backed by ZScript, but as a proof of concept it wasn't intended to be a fully designed application.

Today I'll give a few more details and fix a few mistakes I discovered in the initial design.

I incorrectly stated that I probably wouldn't make many more changes, but not only was I wrong, here it is only a few weeks later and I'm already making some changes.

One of the things that I'm changing for a correct design is to implement a proper View class to back the template.

In my original I was considering the <template> as the View, and the logic indicated by using () was assumed to be within the context of the ViewModel.

But, that is incorrect. Any logic within the template should be within the context of a View. The inconsistency in the design was obvious when I made this statement:

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

Why would we have an Observable<HTMLElement> as a property on a ViewModel when it's obviously View-like?

I also improved the way I instantiate child widgets. Originally my view template had this div:

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

The improvement is to have a child widget which can accept a ViewModel as an argument:

<full-name vm=(vm)></full-name>

And then the child widget template looks like this:

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

The implementation of FullNameView is extracted from the original implementation with minor modifications:

(def FullNameView
  (type
    (elements [
      vm:PersonDetailViewModel
    ])
    (methods [
      fullName:Text:const [] (.text .offDOM builder
        (join (.firstName vm) (.lastName vm) " "))
    ])))

The resulting TypeScript code:

import { IHTMLBuilder } from "../interfaces";
import { PersonDetailViewModel } from "./PersonDetailViewModel";
import { join } from "../stringUtils";

export class FullNameView {
  private vm: PersonDetailViewModel;

  constructor(private builder: IHTMLBuilder, private attrs: any) {
    this.vm = attrs.vm;
  }

  private fullName(): Text {
    return this.builder.offDOM.text(join(this.vm.firstName(), this.vm.lastName(), " "));
  }

  public render() {
    this.build(this.builder);
  }

  private build(builder: IHTMLBuilder) {
    builder.div((builder: IHTMLBuilder) => {
      builder.element(this.fullName());
    });
  }
}

Type Modifiers

You'll notice that fullName:Text has a new modifier, which I'm currently calling const, but that will probably change.

The intention is to have three different modifiers that indicate if the type is Observable, Subject, or a regular value that is immutable (maybe instead of const it should be immut.).

Protocol Elements

You'll notice that in this example I'm passing vm as a PersonDetailViewModel to the child FullNameView widget, but in reality this should be a protocol which has two elements (firstName and lastName), but my current implementation only allows methods to be defined within a protocol. Yet another gap that I need to close.

Maybe I should call the new protocol IHasAFirstNameAndLastName? Better yet, why not make the code generator figure it out and support (elements [vm:auto]), and the code generator sees that vm requires firstName and lastName elements or methods.

Ok, maybe not, since then I wouldn't be able to deduce the type of firstName and lastName. The join function takes a string or an Observable<string>, which means I'd have to generate code that could handle both types. Although in this case that wouldn't be terribly difficult, the eventual solution would lead me down the path of implementing generics, which I'm not quite ready to do.

Food for thought.

At any rate, thanks for taking the time to read! As always, feel free to e-mail me at trichards at indiezen.com.

Happy coding!