Wijmo

Wijmo WebComponents 101

This page shows how to get started with Wijmo WebComponents.

Introduction

This sample introduces Beta version of the Wijmo WebComponents v1 interop , which exposes Wijmo controls as WebComponents. Wijmo web components allows you to declaratively add Wijmo controls to HTML page, and manipulate them as usual DOM elements, without the use of any additional framework. Some browsers that still don't fully support WebComponents standards may require some additional polyfills.

If you are not familiar with WebComponents, here's the good introduction to this technology.

The key aspects of the current state of the Wijmo WebComponents implementation:

  1. Wijmo "top-level" web components that represent Wijmo controls, like wjc-flex-grid component representing FlexGrid control, are inherited from the corresponding control classes. For example, WjcFlexGrid component class extends FlexGrid control class.

    This also means that the base Wijmo Control class extends the HTMLElement class when Wijmo is used in the "WebComponents mode".

  2. Child components which are complementary to top-level components, like wjc-flex-grid-column components that define FlexGrid columns, function as wrappers over Wijmo classes they represent. Child components are directly inherited from the HTMLElement class, and the underlying Wijmo class instance is accessible via the special control property of the component.
  3. Wijmo class properties can be defined using attributes on the component element. When attribute value changes, the corresponding Wijmo class property is updated accordingly. Changing the class property value doesn't entail changing of the corresponding attribute value. This probably can be changed in the future, but right now we have no a decision on whether it's necessary.
  4. JavaScript code can subscribe handlers to Wijmo control events using native Element.addEventListener('event', handler) method.
  5. Wijmo components don't use Shadow DOM now. This will be addressed in the future versions of the interop. One of the challenges here is that Wijmo allows deep customization of its control's parts via CSS, whereas the goal of Shadow DOM is to prevent it. There are new proposals to the Shadow DOM specification that can mitigate this limitation. We continue to keep track the changes. For now, Wijmo web components and their parts can be customized in the same way as ordinary Wijmo controls, using global CSS.

With Wijmo web components interop, you can be declaratively add controls to HTML markup, as shown in this example:

HTML
<label>InputNumber</label> <wjc-input-number value="5" step="3"></wjc-input-number> <label>FlexGrid with Filter and specific columns</label> <wjc-flex-grid id="gridIntro"> <wjc-flex-grid-filter></wjc-flex-grid-filter> <wjc-flex-grid-column binding="country" header="Country" width="*"> </wjc-flex-grid-column> <wjc-flex-grid-column binding="date" header="Date"> </wjc-flex-grid-column> <wjc-flex-grid-column binding="downloads" header="Downloads"> </wjc-flex-grid-column> </wjc-flex-grid>
JS
let gridIntro = document.getElementById('gridIntro'); gridIntro.itemsSource = dataSvc.getData(100);

Result (live):

Setup

This sample is based on npm modules, and is configured to run using either SystemJS run-time module loader or Webpack bundler.

The key aspects of the WebComponents based application configuration are as follows:

  1. Install the latest Wijmo nightly build in NodeJS command prompt:
    > npm install wijmo@nightly --save
  2. Install WebComponents polyfills:
    > npm install @webcomponents/webcomponentsjs --save
    and add them to the root (default.htm) page.

    This polyfill is only necessary if you run application with web components as ES5 code:

    <script src="node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>

    This polyfill is necessary for all browser except for Chrome:

    <script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
  3. Enable Wijmo "WebComponents mode" using the following assignment in the root html page, which must be performed before any Wijmo module has been loaded:
    <script>
        window['wj-control-is-element'] = true;
    </script>

    This assignment will cause the base Wijmo Control class to extend the HTMLElement class, instead of the Object class as it normally happens.

  4. Import necessary modules of Wijmo WebComponents interop in ES6/TypeScript code:
    import 'wijmo/wijmo.webcomponents.input';
    import 'wijmo/wijmo.webcomponents.grid';

    These imports will cause the modules to globally register comprising web components using the window.customElements.define(...) method.

  5. (Not applicable to this sample!) If you want to use Wijmo WebComponents interop as a global module, you need to add the wijmo.webcomponents.js file located in the dist/interop/webcomponents folder of Wijmo download zip to the root html page using the <script> tag:
    <script src="path_to_wijmo/dist/interop/webcomponents/wijmo.webcomponents.js"></script>

    This single file includes all the components provided by the interop.

Now, you can use Wijmo web components in your application:

HTML
<head> <script src="node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script> <script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script> <script> window['wj-control-is-element'] = true; </script> ... </head> <body> <wjc-input-number value="5" step="3"></wjc-input-number> </body>
JS
import 'wijmo/wijmo.webcomponents.input';

Result (live):

Attributes

Property values can be specified using corresponding attributes defined on the Wijmo web component element.

The following rules should be considered:

  1. Attribute names should be in the dash delimited format, for example first-day-of-week should be used to specify a value for the firstDayOfWeek property.
  2. Only simple type properties can be specified as attributes. For enum type properties the enum member names can be used, for example selection-mode="Day".
  3. Because attribute values are always strings, Wijmo web components perform an appropriate value conversion before assigning the value to the corresponding property.

The following example shows a wjc-calendar web component with the specified properties of different types.

HTML
<label>Calendar</label> <wjc-calendar style="width:450px" show-header="true" value="04/07/2018" first-day-of-week="1" selection-mode="Day" format-day-headers="dddd"> </wjc-calendar>
JS
import 'wijmo/wijmo.webcomponents.input';

Result (live):

Child components

In addition to "top-level" web components representing Wijmo Controls, there are also child components that help to declaratively customize its parent component's setup. For example, you can specify a list of FlexGrid columns using the wjc-flex-grid-column components, or define FlexChart series using the wjc-flex-chart-series child components.

Child components must be immediate children of their parent component.

The example below demonstrates:

  1. wjc-flex-grid component with child wjc-flex-grid-column components defining a list of grid columns, and a wjc-flex-grid-filter child component that applies columns filter to the grid.
  2. wjc-flex-chart component with child wjc-flex-chart-series components defining chart's series, and wjc-flex-chart-axis child components that customize chart's X and Y axes.
HTML
<label>FlexGrid with Columns and Filter</label> <wjc-flex-grid id="gridWithChildren" style="height:auto"> <wjc-flex-grid-filter></wjc-flex-grid-filter> <wjc-flex-grid-column binding="country" header="Country" width="*"> </wjc-flex-grid-column> <wjc-flex-grid-column binding="date" header="Date"> </wjc-flex-grid-column> <wjc-flex-grid-column binding="downloads" header="Downloads"> </wjc-flex-grid-column> <wjc-flex-grid-column binding="sales" header="Sales" format="c2"> </wjc-flex-grid-column> </wjc-flex-grid> <label>FlexChart with Series and Axes</label> <wjc-flex-chart id="chartWithChildren" binding-x="country" style="height:300px"> <wjc-flex-chart-axis wj-property="axisX" title="Countries" axis-line="true"> </wjc-flex-chart-axis> <wjc-flex-chart-axis wj-property="axisY" title="Sales/Downloads" axis-line="true" major-grid="true"> </wjc-flex-chart-axis> <wjc-flex-chart-series name="Sales" binding="sales"> </wjc-flex-chart-series> <wjc-flex-chart-series name="Downloads" binding="downloads"> </wjc-flex-chart-series> </wjc-flex-chart>
JS
let chData = dataSvc.getData(dataSvc.countries.length); let gridWithChildren = document.getElementById('gridWithChildren'); gridWithChildren.itemsSource = chData; let chartWithChildren = document.getElementById('chartWithChildren'); chartWithChildren.itemsSource = chData;

Result (live):

Referencing and using in code

You can find a Wijmo web component element using conventional DOM api like the document.getElementById method. After that you can access Wijmo specific API of this element by following the next rules:

  1. For top level components representing Wijmo Controls all the control's API is available right on the element, because such components are directly inherited from the corresponding control class. For example, you can use the following code to assign data to the FlexGrid web component:
    document.querySelector('wjc-flex-grid').itemsSource = getData();
  2. For child components accompanying Wijmo Control components you should use special control property to get a reference to the underlying Wijmo object, because Wijmo child components function as wrappers around the Wijmo classes they represent. For example, this code can be used to specify filter column list for the FlexGridFilter web component:
    document.querySelector('wjc-flex-grid-filter').control.filterColumns = ['country', 'date'];

The following example attaches wjc-group-panel component to the wjc-flex-grid component, as well as defines filter column list for the wjc-flex-grid-filter component.

HTML
<label>FlexGrid with GroupPanel and Filter</label> <wjc-group-panel id="panelRef" placeholder="Drag columns here..."> </wjc-group-panel> <wjc-flex-grid id="gridRef"> <wjc-flex-grid-filter id="filterRef"></wjc-flex-grid-filter> </wjc-flex-grid>
JS
// Find grid element in the DOM tree let gridRef = document.getElementById('gridRef'); // The element is also a FlexGrid instance (inherited from FlexGrid), // so we can use FlexGrid api on the element, e.g. to assign its data // source. gridRef.itemsSource = dataSvc.getData(100); // Find group panel element in the DOM tree let panelRef = document.getElementById('panelRef'); // and attach it to the grid: panelRef.grid = gridRef; // Find wjc-flex-grid-filter component in the DOM tree let filterRef = document.getElementById('filterRef'); // and specify the filterColumns property value of its // underlying FlexGridFilter object filterRef.control.filterColumns = ['country', 'date'];

Result (live):

Events

You can subscribe to Wijmo specific events using conventional addEventListener method, as you do with the native HTML elements. The event name should be specified in the dash delimited format, for example value-changed for the native Wijmo valueChanged event.

For example, you can subscribe to the InputNumber.valueChanged event of the corresponding web component using the code like this:

document.querySelector('wjc-input-number').addEventListener('value-changed', (e) => {
    alert(`New value is ${e.target.value}`);
});

Wijmo components trigger events of the CustomEvent type. There are two key properties of the event object that can be used in the event handler:

  1. target - references the web component that triggered the event.
  2. detail - holds event arguments of the corresponding Wijmo event. For example, for the FlexGrid.formatItem event the CustomEvent.detail property will contain an object of the FormatItemEventArgs type.

Note that Wijmo web components representing Wijmo Controls inherit two different versions of the addEventListener methods from the two base classes - wijmo.Control implements its own version of this method, and it also inherit this method from the base HTMLElement class. Because these two incarnations of this method have different signatures, Wijmo web components are capable to differentiate them and call an appropriate implementation of this method.

The following example uses value-changed event of the wjc-input-number and wjc-linear-gauge components to synchronize their values. It also uses wjc-flex-grid's format-item event to customize cell content of the Active column.

HTML
<label>InputNumber and LinearGauge synchronized using valueChanged event</label> <wjc-input-number id="inpNumEvents" value="30" step="10" min="0" max="100"> </wjc-input-number> <wjc-linear-gauge id="gaugeEvents" value="30" step="10" min="0" max="100" is-read-only="false" is-animated="false" thumb-size="10" style="display:block;height:30px"> <wjc-range wj-property="face" thickness="0.25"></wjc-range> <wjc-range wj-property="pointer" thickness="0.25"></wjc-range> </wjc-linear-gauge> <label>FlexGrid's Active column formatted using formatItem event</label> <wjc-flex-grid id="gridEvents"> <wjc-flex-grid-column binding="country" header="Country" width="*"> </wjc-flex-grid-column> <wjc-flex-grid-column binding="downloads" header="Downloads"> </wjc-flex-grid-column> <wjc-flex-grid-column binding="active" header="Active"> </wjc-flex-grid-column> </wjc-flex-grid>
JS
// InputNumber and LinearGauge synchronized using valueChanged event let inpNumEvents = document.getElementById('inpNumEvents'); let gaugeEvents = document.getElementById('gaugeEvents'); inpNumEvents.addEventListener('value-changed', (e: CustomEvent) => { // CustomEvent.target references the component where event occurred gaugeEvents.value = (e.target).value; }); gaugeEvents.addEventListener('value-changed', (e: CustomEvent) => { inpNumEvents.value = (e.target).value; }); // FlexGrid's Active column formatted using formatItem event let gridEvents = document.getElementById('gridEvents'); gridEvents.itemsSource = dataSvc.getData(100); gridEvents.addEventListener('format-item', (e: CustomEvent) => { // CustomEvent.detail contains corresponding Wijmo event arguments let args = e.detail, grid = args.panel.grid; if (args.panel === grid.cells && grid.columns[args.col].binding === 'active' && !args.range.equals(grid.editRange)) { args.cell.innerHTML = grid.getCellData(args.row, args.col, false) ? 'Yes' : 'No'; } });

Result (live):

Creating programmatically

You can operate with Wijmo web component elements using standard DOM API, in the same way as you do it with native HTML element. Specifically, you can:

  1. Create a Wijmo component using document.createElement method, for example document.createElement('wjc-flex-grid').
  2. Add, move and remove elements to/from the DOM tree using the methods like document.appendChild, document.insertBefore and document.removeChild.
  3. The same is true for Wijmo child components, for example you can add, remove and reorder grid columns by adding/removing/reordering wjc-flex-grid-column elements in their parent wjc-flex-grid element.

This sample creates a FlexGrid with columns and column filters programmatically, by creating corresponding Wijmo web components and adding them to the appropriate places of the DOM tree.

HTML
<label>FlexGrid with columns and filter created programmatically</label> <div id="gridProg"></div>
JS
// Find placeholder element let placeHolder = document.getElementById('gridProg'); // Create FlexGrid web component let gridProg = document.createElement('wjc-flex-grid'); // Add grid to DOM placeHolder.appendChild(gridProg); // Create Column web component let countryCol = document.createElement('wjc-flex-grid-column'); // add child to parent before assigning its properties gridProg.appendChild(countryCol); countryCol.control.binding = 'country'; countryCol.control.header = 'Country'; countryCol.control.width = '*'; // Create Column web component let downloadsCol = document.createElement('wjc-flex-grid-column'); // add child to parent before assigning its properties gridProg.appendChild(downloadsCol); downloadsCol.control.binding = 'downloads'; downloadsCol.control.header = 'Downloads'; // Create FlexGridFilter web component let filter = document.createElement('wjc-flex-grid-filter'); // add child to parent before assigning its properties gridProg.appendChild(filter); filter.filterColumns = ['country']; // Set grid data (gridProg).itemsSource = dataSvc.getData(100);

Result (live):