DEV Community

yysun
yysun

Posted on

Advanced View Features in AppRun

Since the AppRun book was published, there are new features added to AppRun, such as ref, element embedding, and directive. This post describes the advanced usage of these new features.

If you are new to AppRun, it is a JavaScript library for building web applications using the Elm inspired architecture, events, and components. Its architecture concept is to is broken down the application logic into three separate parts.

  • State (a.k.a. Model) — the state of your application
  • View — a function to display the state
  • Update — a collection of event handlers to update the state

AppRun ties the three parts together and drives the applications using events.

Introduction

AppRun support to HTML string, lit-html, and JSX in the view function. We recommend using JSX. The advanced features apply to JSX only.

ref

ref is a special JSX property, which is a callback function that is called after the view function is executed.

const view = <div ref={el=>{...}}></div>

We can use ref function to update the HTML element, e.g., creating d3 charts to the element. It provides a better method to the one described in the AppRun book, which is to use the rendered lifecycle function.

Please think of using ref function, before you use the rendered function.

Element embedding

Furthermore, AppRun allows embedding elements directly into JSX.

view = state => {
  const canvas = document.createElement('canvas');
  return <div>{canvas}</div>
};

We don't need the rendered lifecycle function nor the ref function to integrate with other 3rd libraries. Just create the HTML element and add it to the AppRun view.

Please think of embedding the element before you use the ref function.

The HTML element can be cached to avoid recreation in every event lifecycle, which will be explained below.

Directive

The directive is the special property that looks like $xxx. When AppRun is processing the JSX code and finds the properties of $xxx, it publishes the $ event. The event parameters contain the directive key, properties, and tag Name of the HTML element, and component instance.

const view = <div $myDirective></div>;
app.on('$', ({key, props, tag, component}) => {
  if (key === '$myDirective') {
  }
}

We can subscribe to the $ event and create custom directives to modify the properties of the HTML element, e.g., adding or removing classes.

Now let's see some use cases of the features described above.

Example App

The admin dashboard app is an AppRun app built with Bootstrap. The app uses a few 3rd party libraries such as Chart.js, d3, and DataTables.

https://apprunjs.github.io/apprun-bootstrap/

Chart

Chart.js creates charts using the canvas element. We create a canvas element and then use Chart.js to create a chart. We embed the canvas directly into JSX.

export default class extends Component {
  state = { data: {} }
  view = state => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    state.chart = new Chart(ctx, state.data);
    return <div>{canvas}</div>;
  };

  unload = state => {
    state.chart?.destroy();
    console.log('chart destroyed');
  };
}

We also store the Chart object in the state, so that we can destory when the HTML element the component attached to is remove.

Map

D3 creates maps using the svg element. The height and width of the map are based on the height and width of the svg element. We use the ref function to retrieve the svg element for map creation.

const map = (element, features) => { ... }

export default class extends Component {
  view = state => <svg ref={el => map(el, state.features)}></svg>;
}

We can cache the HTML to avoid re-creating it. Using the ref function, we store the HTML element into the state. We can reuse the stored element inside the view function if it presents.

view = state =>
  state.map_card || (
    <Card header={<div>D3 Map</div>} ref={el => (state.map_card = el)}>
      <svg ref={el => map(el, state.features)}></svg>
    </Card>
  );

Data Table

DataTables is a plug-in for the jQuery. We create a custom directive called $data-table to attach a ref function, which makes the DataTables out of the table element.


// checkout available options: https://datatables.net/reference/option/
const DataTable = options => table =>
  !$.fn.dataTable.isDataTable(table) && $(table).DataTable(options);

app.on('$', ({ key, props }) => {
  key === '$data-table' && (props.ref = DataTable(props[key]));
});

Then, we can use the $data-table to make any table into a data table.

export default class extends Component {
  state = { data };

  view = state => <table
    class="table w-100 table-striped table-bordered"
    $data-table={{
      data: state.data,
      columns,
      pageLength: 10
    }}
    />
  );
}

In our real-world business applications, we use the $data-table directive on many components/pages. It is very convenient.

We also can use the same method to integrate other jQuery plugins such as auto-complete and date-picker.

Finally, We add the caching to the data table as we did for caching the map. I believe you can recognize the pattern used below.

export default class extends Component {
  state = { data };

  view = state => state.dt || <div ref={el => state.dt = el}>
    <table
      class="table w-100 table-striped table-bordered"
      $data-table={{
        data: state.data,
        columns,
        pageLength: 10
      }}
      />
  </div>;
}

The benefit of caching the data table is that the UI state of the data table is preserved. You can search, filter, or select the page of the data table. Then you go to another page. When coming back, the data table has the content and page remain as you selected. The data table is not re-created/re-rendered.

You can run the example app from https://github.com/apprunjs/apprun-bootstrap. Or checkout it repository:

GitHub logo apprunjs / apprun-bootstrap

AppRun Admin Dashboard

AppRun Admin Dashboard

Screenshot

Have fun and send pull requests!

(C) Copyright Yiyi Sun

Conclusion

Newly added AppRun features: ref, element embedding, and directives provide much more power to the developers for building web applications using less code and in a declarative way. Please use the new features going forward.

Latest comments (0)