Component rendering

Overview

Like most modern frameworks, Tui and Vue are built on the idea that the UI can be created by describing what it should look like based on your UI state, or put another way, that it is a function of your UI state - you put in data, and get out what the UI should look like. You change some data, and the framework handles updating the UI by calculating the difference between the two generated descriptions and applying it.

In this way, you're freed from writing code to manually update each part of the UI when data related to it has changed, as Vue will do this for you automatically and efficiently.

Another key point is that the UI is divided up in to composable and reusable components, which define their own UI and manage their own rendering.

Implementation

Component rendering begins when the page loads, as component placeholders inserted in to the page by the PHP rendering code are located and replaced with the component itself.

Components typically also render many other components, and there are two ways in which this happens. Typically, components are just imported in the script section and used in the template.

There are some special requirements if you want to use a Vue component or JavaScript module from another Tui component - e.g. using a Vue component from totara_comment in mod_custom. If it is a static dependency (i.e. you know what component you need ahead of time), you can add it to dependencies in tui.json, e.g. "dependencies": ["totara_comment"] and that bundle will be loaded alongside the bundle for your Tui component.

If you don't know what component you want ahead of time, for example if its name is returned by an API call, or want to delay loading the bundle it is contained in until it is actually used, you can instead make use of the tui.asyncComponent API. This function returns a component that can be used with <component :is=""> or any other Vue rendering mechanism, but will just display a loading icon until it is actually available. This is especially useful for dynamically specified components, e.g. from a plugin. The dynamic component could be anything, from part of a Form to an entire component tree such as the contents of a modal.

See the JavaScript page for more information on asynchronous loading of code and components.

<template>
  <div>
    <Button text="Show asynchronous component" @click="showAsyncComponent" />
    <component :is="component" />
  </div>
</template>
<script>
import Button from 'tui/components/buttons/Button';

export default {
  data: { component: null },
  methods: {
    showAsyncComponent() {
      this.component = tui.asyncComponent('mod_foo/components/Example');
    },
  }
};
</script>

We also make use of slots, a mechanism in Vue that allows you to pass content to another component and let that component choose where to render it - for example a modal dialog component might have a slot for the title and another slot for the content.

Tips and known limitations

  • Vue automatically re-renders components when their data changes, however there are some changes that Vue cannot detect without a bit of help - namely adding a new field to an object or directly setting an array item's value with the indexer syntax. You can use Vue.set() to get around this.
  • Only modifications to data that Vue is observing through its reactivity system, such as the result of the data() initialiser or component props will trigger a re-render. If you need to base rendering on data that is not covered by this, you can do so by creating an observable object by passing it through the Vue.observable() function.

Recommended reading