View

A view is the visual part of a component, and it consists of an .html template file, and a .css file, both with the same base name:

A view can also be implemented inline, rather than in separate files, and can be declared by adding the z-view attribute to its container element.

The value of z-view attribute is meant to be a unique identifier ([<path>/]component_name) and should not match the identifier of any other view placed in a file or inline in the same page, unless we want to override it.

Inline declaration of the view inline/example/hello-world:

<!-- HTML Template -->
<template z-view="inline/example/hello-world">
  <h1>
    Hello World!
  </h1>
  <!-- CSS styles -->
  <style>
    h1 {
      color: steelblue;
      font-weight: 300;
    }
  </style>
</template>
<!-- HTML Template -->
<div z-view="inline/example/hello-world">
  <h1>
    Hello World!
  </h1>
  <!-- CSS styles -->
  <style media="#">
    h1 {
      color: steelblue;
      font-weight: 300;
    }
  </style>
</div>
<!-- HTML Template -->
<div z-view="inline/example/hello-world">
  <h1>
    Hello World!
  </h1>
</div>
<!-- CSS styles -->
<style media="#inline/example/hello-world">
h1 {
  color: steelblue;
  font-weight: 300;
}
</style>

the above code is just a declaration of a view and will not produce any visible content, to actually load an instance of the hello_word view, the following code is used:

<div view z-load="inline/example/hello-world"></div>

or, if a custom element tag has been defined like in the example below,

customElements.define('hello-world', class extends HTMLElement {
  connectedCallback() {
    zuix.loadComponent(this, 'inline/example/hello-world', 'view');
  }
});

then the view template can be loaded using the custom element tag:

<hello-world></hello-world>

Hello World!

Data binding

In the previous example, the text "Hello World" is static and cannot be customized, but an HTML template can also have elements of the view whose value is bound to fields of a data model that can be provided with the component's loading options.

To bind the value of an element in the HTML template to a field of a data model, the #<field_name> attribute (shorthand of z-field="<field_name>"), is added to the element in order to specify the name of the corresponding field in the data model.

Declaration of an inline view with title and subtitle fields:

<div z-view="inline/example/article-header">
  <h1 #title>
    Example title
  </h1>
  <p #subtitle>
    Example article subtitle
  </p>
</div>

Example title

Example article subtitle

Then, to set the actual data to display in #<field_name> elements, the :model attribute can be added to the host element, to pass a JSON data object with the actual data to display:

<div view z-load="inline/example/article-header" :model="{
  title: 'Image from Mars',
  subtitle: 'A Perseverance rover scientist’s favorite shot of Jezero Crater\'s \'Delta Scarp\'.'
}"></div>

It's also possible to map a field with a different name, other than <field_name>, by specifying a value for the # attribute with the name of the mapped field: #<field_name>="<mapped_field_name>" (equivalent of z-field="<field_name>" z-bind="<mapped_field_name>").

To declare a data model with nested fields, dotted names syntax can be used, like with link.url and link.title fields in the following template example of a Material Design Lite card.

templates/mdl-card.html

<!-- Template of a Material Design card -->
<div class="mdl-card mdl-shadow--8dp">
  <div class="mdl-card__title">
    <img #image src="examples/images/card_placeholder.jpg" class="portrait" alt="Cover image">
    <h1 #title class="mdl-card__title-text mdl-color-text--white animate__animated animate__fadeInDown">
      Card title
    </h1>
  </div>
  <div #text class="mdl-card__supporting-text animate__animated animate__fadeInUp">
    Lorem ipsum dolor sit amet, consectetur adipisicing elit.
    Aliquam accusamus, consectetur.
  </div>
  <div class="mdl-card__actions mdl-card--border animate__animated animate__fadeInRight">
    <a #url="link.url" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-color-text--accent">
      <span #link.title>
        Open
      </span>
    </a>
  </div>
</div>

When loading a view, the data provided through the :model attribute, can be passed in either of the following ways:

<div view z-load="templates/mdl-card" :model="{
  title: 'Down the rabbit hole',
  image: 'examples/images/card_cover_2.jpg',
  text: 'Luckily for Alice, the little magic bottle had now had its full effect, and she grew no larger&hellip;',
  link: {
    title: 'Read more',
    url: '#'
  }
}" class="visible-on-ready"></div>
<div view z-load="templates/mdl-card" :model="myCardData"></div>
<script>
myCardData = {
  title: 'Down the rabbit hole',
  image: 'examples/images/card_cover_2.jpg',
  text: 'Luckily for Alice, the little magic bottle had now had its full effect, and she grew no larger&hellip;',
  link: {
    title: 'Read more',
    url: '#'
  }
};
</script>

Binding Adapters

Sometimes it might be required to have more control over how the value of a data model's field affects the view. For this purpose binding adapters can be used.

A binding adapter is a function that gets called to update bound elements of a view, based on the actual values of the data model's fields.

A binding adapter gets automatically called everytime a new data model is set, or if the context.modelToView() method is called programmatically. In both cases, the binding adapter function, will get called for each #<field_name> or z-bind attribute declared in the view's template and will provide required code to update the view:

/** @type {BindingAdapterCallback} */
function vm_binding_adapter($element, fieldName, $view, refreshCallback) {
  // TODO: adapter code to update `$element` bound
  //       to `fieldName` data model's field
}

where $element is the element of the view with #<field_name>/z-bind attribute, fieldName is the attribute value, that is the name of the bound field in the data model, and $view is the view's element itself.

The refreshCallback is a callback that can be used either for postponing the update of field's bound $element (eg. data is not available yet), or to request a cyclic refresh of the same $element/fieldName.

<div view z-load="templates/mdl-card"
     z-model="sampleCardAdapter"></div>

<script>
counter = 0;
sampleData = null; // example data for the card

// let's pretend the data is fetched asynchronously via http
setTimeout(() => {
  sampleData = {
    title: () => 'Hello! ' + counter++,
    text: 'A binding adapter can be used to customize the way data model\'s fields are mapped to the view elements.',
    image: () => 'https://picsum.photos/320/160?' + counter
  }
}, 1);

function sampleCardAdapter($element, fieldName, $view, refreshCallback) {

  if (sampleData === null) {
    // no data yet
    // return and retry again in 500ms
    return refreshCallback();
  }

  // data is available, update only
  // if the `$element` is visible on screen
  if (!$element.position().visible) {
    // postpone field binding of 1s if element is not visible
    return refreshCallback(1000);
  }

  // $element is visible, resolve field binding
  switch(fieldName) {

    case 'title':
      $element.html(sampleData.title());
      // this field will be updated every second
      return refreshCallback(1000);

    case 'image':
      $element.attr('src', sampleData.image());
      // get the component's context
      const ctx = zuix.context($view);
      // to animate the `title` field also
      ctx.field('title')
         .playAnimation('animate__fadeInDown animate__bounce');
      // this field will be updated every 5 seconds
      return refreshCallback(5000);

    case 'text':
      $element.html(sampleData.text);
      // no `refreshCallback`, this field
      // will be updated only once
      break;
  }

}
</script>

In the above example the binding adapter is used to handle all bound fields of the data model. This binding adapter also checks if the component is visible before updating view's fields. If it's not visible then it will enter an idle state postponing the refresh.

avatar
Contact Name

In the case of a JSON data model, it's also possible to use a binding adapter for each single field, like with the field name in the following example:

<!-- Foo Bar chip -->
<div view z-load="inline/common/contact_chip"
     :model="foo_bar_contact" style="min-height: 36px"></div>
<!-- Jane Doe chip -->
<div view z-load="inline/common/contact_chip"
     :model="a_random_contact" style="min-height: 36px"></div>

<script>
// example inline data model
foo_bar_contact = {
  image: 'images/avatar_02.png',
  name: 'Foo Bar'
};
// the field `name` is updated using
// a binding adapter
a_random_contact = {
  image: 'images/avatar_01.png',
  name: function($element, field, $view, refreshCallback) {
    const name = a_random_name();
    $element.html(name);
  }
};
function a_random_name() {
  // let's pretend this is random =))
  return 'Jane Doe';
}
</script>

Accessibility

The data model can also be set directly inside the host element through HTML tags, and this will provide a default visualization in case the view's template is still loading or JavaScript is not enabled.

The following example shows how to embed data model's fields with HTML code inside the host element using #<field_name> attribute:

<div view z-load="templates/mdl-card">

  <h1 #title>Let's code!</h1>
  <img #image src="examples/images/card_cover_3.jpg"
              alt="Cover image" role="presentation" width="460">
  <p #text>
    Yes we can!
  </p>
  <a #link.url href="#">
    <span #link.title>Take me there</span>
  </a>

</div>

and the above code, as is, will also provide a default visualization, that will also work without JavaScript:

Let's code!

cover

Yes we can!

Take me there
  • Load view "mdl-card"
  • Unload

to actually load or unload the templates/mdl-card view, use the Try Me button above.

This is just an example to show what happen when the HTML view code is enhanced by the component, since by default, in JavaScript enabled browsers, components loading starts right away.

So, in the previous example, the component data model fields are HTML elements within the host element (attributes with the # prefix), which are automatically mapped to a certain property of the corresponding element in the loaded component's view, depending on its type.

For instance, if the target element is img, then the mapped property will be .src, while if it's a div or a p, it will be the .innerHTML property. A binding adapter can eventually be used to override the default elements mapping strategy.

The host element body can also be used to simply provide an alternative text description for browsers where Javascript is disabled, or a loading message to show while the component is loading for browsers where Javascript is enabled:

<div view z-load="templates/mdl-card" :model="{
  title: 'Some title',
  image: 'examples/images/card_cover_4.jpg',
  text: 'Some great encouraging text.',
  link: {
    title: 'Just do it!',
    url: '#'
  }
}">
  <h1>-=| loading |=-</h1>
  <div class="mdl-spinner mdl-js-spinner is-active"></div>
  <noscript>
    This component works only in JavaScript enabled browsers.
  </noscript>
</div>

-=| loading |=-
  • Load view "mdl-card"
  • Unload

Since components can be dynamically loaded, unloaded or replaced, it is also possible to select a specific layout of the component, based for instance, on the device screen size/orientation or a user selectable theme. All without changing the HTML code of the page, that will basically host the components' data models itself, and also provide through it a default visualization of the page that will even work without Javascript.

Behaviors

Behavior Handlers determine how a view will react and behave upon certain events, like user interaction or state-change events. It's a different thing from regular events, since behavior are highly generic and reusable on groups or categories of elements. They are also very different from CSS animations, since behind behaviors there is a user-definable logic implemented with JavaScript code.

In a similar way to Event Handlers, Behavior Handlers can be declared in the component's options through the property behavior:

<script>
options = {
  // behavior handlers
  behavior: {
    '<event_name>': function(e, data, $el?) {
      // the context object `this` is same as `$el`
    },
    // other behaviors ...
  },
  // event handlers
  on: { /* ... */ }  
  // other component's options...
}
</script>
<div z-load="my/component" :options="options"></div>

where <event_name> is the name of the event (eg. click, mouseover, ...), and function(e, data, $el?) is the associated EventCallback that will be called each time the <event_name> occurs.

Behavior Handlers can also be directly declared with the :behavior attribute on the host element:

<script>
componentBehavior = {
    '<event_name>': function(e, data, $el?) {
        // the context object `this` is same as `$el`
    },
    // other behaviors ...
};
</script>
<div z-load="my/component" :behavior="componentBehavior"></div>

Using the internal default component, it is possible to get advantage of behaviors and other component's features, also on standard HTML elements as shown in the example below, where a standard input element is enhanced with a visual feedback to report user input errors using HTML5 built-in form validation.

<input :behavior="checkValidityBehavior"
       type="text" value=""
       placeholder="Enter nickname"
       pattern="[a-zA-Z0-9]+" minlength="4" maxlength="10"
       message="Choose a name between 4 and 10 chars long, only letters and numbers."
       aria-describedby="input-error">

<small id="input-error"></small>

The behavior CheckValidityBehavior in this example, checks validation properties of the input element, and reports validation hints inside the element with the id specified by the aria-describedby attribute. The message attribute is used instead to configure a default hint message to show when the input value is empty.

In this other example, a view switch behavior is applied to some buttons and text elements. Each of them, also specify the target view associated to be shown on click, and a transition animation to be used. So, when a view switch button is clicked, the associated view will be shown using the transition animation effect specified or a default fade. ViewSwitchBehavior takes one argument that can be used to specify the transition direction that can be 'horizontal' (default), or 'vertical'.

<script>
view_switch_h = ViewSwitchBehavior('horizontal');
view_switch_v = ViewSwitchBehavior('vertical');
</script>

<!-- Clicking the "1" button, will show "view-1" -->
<button :behavior="view_switch_h"
        target="view_1" animation="bounce">
    1
</button>

<!-- Clicking the "2" button, will show "view-2" -->
<button :behavior="view_switch_h"
        target="view_2">
    2
</button>
// ...

<!-- the pictures container --->
<div style="overflow: hidden; position: relative; width: 320px;height: 240px;">

    <div #view_1>
        <img src="picture-1.jpg">
    </div>

    <div #view_2 tab-selected="true">
        <img src="picture-2.jpg">
    </div>
    // ...

</div>

The possible values of the animation attribute are:

'bounce', 'fade', 'slide', 'zoom', 'back',
'lightSpeed' // (<-- this one only works with horizontal orientation)

Random image

Random content 1

Random image

Random content 2

Random image

Random content 3

Random image

Random content 4

Random image

Random content 5

Random imageRandom imageRandom imageRandom imageRandom image
Ok, Let's try this behavior on a simple text span ...
TEST one, two, three, four, five! ... =)
Context Options
Events and Active→Refresh
GitHub logo
JavaScript library for component-based websites and applications.