FOAM Views

It’s very unlikely that the default list row view is going to serve for your app. It looks at your model, and guesses at an interesting, string-ish property to put in the row.

For our Todo model, it guessed title. That’s a good guess, but we’d like to have the isCompleted checkbox next to each item in the list.

View basics

FOAM’s U2 view library is based on foam.u2.Element. Element has several methods for building DOM elements and wiring up reactive values. There is also a template syntax, which we will use here.

foam.u2.View is an important subclass of Element. It adds a data, property, representing the object currently being viewed. data might be a modeled object, or a string or other Javascript value.

Creating our view

First, let’s make a new directory PROJECT/js/com/todo/u2, with a new file TodoCitationView.js:

PROJECT
|- foam/
|- js/
|--- com/
|----- todo/
|------- TodoApp.js
|------- model/
|--------- Todo.js
|------- u2/
|--------- TodoCitationView.js

and fill the file with the following:

CLASS({
  package: 'com.todo.u2',
  name: 'TodoCitationView',
  extends: 'foam.u2.View',
  templates: [
    function initE() {/*#U2
      <div class="^">
        {{this.data.title}}
      </div>
    */},
  ]
});

If you reload the app, you’ll see that nothing changed. That’s because we’re not yet using our new view.

We’ll fix that in a moment, but first, let’s look into what’s going on in that template.

Template Syntax

Any model can have templates, which are run through a parser. The default parser is the old U1 HTML template parser, but we override that here to use the U2 parser.

A full description of the syntax can be found in the template guide, but here’s a brief summary:

  • Javascript doesn’t support multiline strings, so we hack them in with functions whose entire body is a /* block comment */.
  • Adding #U2 right at the start of a template enables the U2 parser.
    • It will become the default soon.
  • In CSS classes, ^ is expanded to the model name with dashes instead of periods.
    • In this case, ^ becomes com-todo-u2-TodoCitationView-.
  • {{ expression }} inserts the result of the expression at that spot.
    • Here we’re putting in this.data.title.
  • (( real JS code )) runs the literal Javascript code.
  • <obj:property ... /> puts the default view for obj.property here.

When the template parser is finished, our TodoCitationView model has an initE method that returns an Element constructed from our template.

External Templates

You can also put templates in separate files, named MyModel_templateName.ft. For example, TodoCitationView_initE.ft.

Then in your model’s templates section, instead of adding an inline template, just add it by name:
{ name: 'initE' }.

Wire in our new view

By default, the Browser uses foam.u2.md.CitationView for the list rows. We’re going to configure it to use our new com.todo.u2.TodoCitationView.

Edit TodoApp.js.

Add 'com.todo.u2.TodoCitationView' to the requires:

requires: [
  'com.todo.model.Todo',
  'com.todo.u2.TodoCitationView',
  'foam.browser.BrowserConfig',
  'foam.dao.EasyDAO',
],

Whenever the list view in the Browser wants to create a row for some object, it checks if that object has a toRowE or toE method. These methods are optional, but if defined they should return an Element.

Edit Todo.js and let’s define toRowE for it.

CLASS({
  package: 'com.todo.model',
  name: 'Todo',
  properties: [
    {
      name: 'id',
      hidden: true
    },
    {
      name: 'title',
    },
    {
      type: 'Boolean',
      name: 'isCompleted',
      label: 'Completed'
    },
  ],

  methods: [
    function toRowE(X) {
      return X.lookup('com.todo.u2.TodoCitationView').create({ data: this }, X);
    }
  ]
});

(Don’t worry about what that X parameter is doing there. We’re hoping to clean up this flow in the future.)

If you reload the app, you’ll see that our change is working - but the app isn’t really improved!

unstyled title

Our title is rendering properly, but it’s not nicely styled, and we don’t have the checkbox yet.

Values and Reactivity

The template parser turns {{expressions}} into a call to Element.add(). add() accepts a variety of things:

  • Elements
  • Strings
  • Anything with a toE() method that returns an Element
  • Values.

In FOAM, a Value is a kind of object-oriented pointer. It has get() and set() methods, as well as addListener().

For every property foo on a model, instances have both foo and foo$. foo$ is a Value for foo.

If you add this.foo to an Element, the current value is copied. If you add this.foo$ instead, the Element listens for changes and updates the DOM.

Two-way binding is achieved by adding an editable view for a property, like we’re about to do for isCompleted.

Adding the Checkbox

Let’s add the checkbox for isCompleted to our TodoCitationView. We’ll also switch to title$, so the title will react properly.

Edit the template to read:

function initE() {/*#U2
  <div class="^">
    <:isCompleted />
    {{this.data.title$}}
  </div>
*/},

Here we’re using the <:someProperty /> syntax to insert a two-way View for a property.

checkbox with label in its own row

It worked, but it still doesn’t look right.

(Under the hood, properties are themselves modeled objects with properties, methods, etc. The above looks up the Property object for isCompleted and checks its view property. For Boolean properties, that defaults to foam.u2.Checkbox, which is what appears.)

Attributes on Views

Views can tag their properties with attribute: true. If a property is tagged as an attribute, the template parser will look for that attribute to be set on the XML tag.

Many views, including Checkbox, support a showLabel property – let’s set it to false in our template:

function initE() {/*#U2
  <div class="^">
    <:isCompleted showLabel="false" />
    {{this.data.title$}}
  </div>
*/},

And now we’re getting closer:

checkbox without label in its own row

We’ll have to add some CSS to finish customizing our citation view.

CSS Templates

In both U1 and U2, models can include a template called CSS. That template’s output will be installed on the document exactly once.

We mentioned the ^ CSS class shorthand earlier. In a CSS template, it expands like it does in an initE template, to eg. .com-todo-u2-TodoCitationView.

The CSS guide explains more about the various features of the CSS templates.

CSS for TodoCitationView

Expand TodoCitationView.js to look like this:

CLASS({
  package: 'com.todo.u2',
  name: 'TodoCitationView',
  extends: 'foam.u2.View',
  templates: [
    function CSS() {/*
      ^ {
        align-items: center;
        border-bottom: 1px solid #e0e0e0;
        display: flex;
      }
    */},
    function initE() {/*#U2
      <div class="^">
        <:isCompleted showLabel="false" />
        {{this.data.title$}}
      </div>
    */},
  ]
});

Now it renders nicely:

checkbox rendering nicely in the row

Next

Our app has come together, at least concerning the basics.

Next we’ll set out to allow separate views of pending, completed and all Todos. Before we can go there, we’ll need to be more familiar with the DAO interface.

We recommend reading the DAO user guide, and then continuing the tutorial with Part 6: Menus.