Job Opportunity - JavaScript Developer
Under Development
This version is currently under development and should not be used in a production environment.

Building Modules

Overview

Modules allow anyone to add custom functionality to Tabulator. By subscribing to internal table events and registering functions on the table and its components you can extend tabulator in any way you like.

This section will explain all the standard features of a module, although without practical demonstration they may seem a bit abstract. You should use this section as a reference for your module building, but consult the Example Modules Guide to see how these features are applied in practice to build a functioning module.

Import The Module Base Class

Building a modules starts by importing the Module class from the Tabulator library.

import {Tabulator, Module} from 'tabulator-tables';

Basic Structure

To create a custom module you need to extend the base Module class and then provide a minimum of two functions inside the class, the constructor and the initialize functions

class CustomModule extends Module{
    constructor(table){
        super(table);

        //register table options
        //register column options
        //register table functions
        //register component functions
    }

    initialize(){
        //subscribe to internal events
        //regsiter data handlers
    }
}

CustomModule.moduleName = "custom";

The Constructor

The constructor is called as the module is being instantiated and is where your module should start to tell tabulator a little about itself. The constructor takes one argument, the table the module is being bound to, it should pass this to the super function so that it is available for the module to bind to its internal helper functions.

It is very important that you do not try any access any parts of the table, any events or other modules when the constructor is called. at this point the table is in the process of being built and is not ready to respond to anything. The constructor should be used to register any external functionality that may be called on the module and to register andy setup options that may be set on the table or column definitions.

Initialization

When the structure of the table is complete and the setup options have been parsed, the initialize function is called on each module. In this function you should check to see if your module has been enabled by the setup options prodivded to the table constructor, intialize your module configuration based on these options and subscribe to any internal table events you may need.

One of the key concepts with modules, is that they only subscribe to events if they need to use them, keeping the amount of processing needed to a minimum, so it is really important that you check that propertys enabling your module in the table options have been set before you subscribe to internal events.

Module Name

The module name property must be declared on the class (not an instance of the class), and be a camelCase name for the module, this is used internally by the table to act as a unique identifier for the module.

Registering the Module

Once you have built out your module, you need to register it with Tabulator using the registerModule function. Makse sure that you do this before you instatiate your first table, or the module will not be available

Tabulator.registerModule(CustomModule);

Internal Components

When building out your module, you will likely need to deal with the rows, columns or cells of the table. In order to do this you will need to work with internal component objects that represent each of these different table components. It is important to note that the internal component objects that will be passed to your module through the various table events are NOT the same as the Component Objects that are available through the public API.

While internal table components have many of the same functions as the external components, they also contain a wide range of properties and functions that are used internally within the table to manage layout and data handling. This section will go over some of the key internal component features that may be needed to build a module.

Module Object

When building a module it may be nessisary to store meta information about the module on a specific component (eg. the edited state of a cell). To help with this each component has an object stored on its modules property that can be used to store module specific data without polluting the modules properties.

If you want to store properties on the modules object, they should be stored in another object on a property that matches the name of your object. For example the edit module stores its data on a edit property:

cell.modules.edit = {
    editable:true,
    editor:"star",
    edited:true,
}

External Component

While internal table components have many of the same functions as the external components, they also contain a wide range of properties and functions that it is not safe to call from outside the table, it is critical that should you register any public functions, callbacks or events that pass components out to the user, that you pass only the external component. This can be achevied by calling the getComponent function on any internal component:

var externalRow = row.getComponent();

Registration

Modules provide a number of functions to allow registration of table and column options an of public functions that can be called on the table and its components.

Option and function registration should take place in the constructor of a module.

Options

Table Option

Too add an option to the table constructor, you can use the registerTableOption function.

This function takes two arguments, the first is the name of the table option, the second is its default value.

this.registerTableOption("ajaxURL", false);

This will then be available in the options list when the table in instatiated

var table = new Tabulator("#example-table", {
    ajaxURL:"http://get-my-data.com",
});

Option Registration
You must register any options that you want to be available in the options constructor object. If you dont an error will be thrown when a user tries to set the option

Unique Name
Options must have a unique name, if you try and register an option that has already been registered by another module, a console error will be thrown

Column Definition Option

Too add an option to the column definition object, you can use the registerTableOption function.

This function takes two arguments, the first is the name of the table option, the second is its default value.

this.registerColumnOption("download", true);

This will then be available in the column defintion options list when the table in instatiated

var table = new Tabulator("#example-table", {
    columns:[
        {title:"Name", field:"name", download:false},
    ]
});

Option Registration
You must register any options that you want to be available in the column definition object. If you dont an error will be thrown when a user tries to set the option

Unique Name
Options must have a unique name, if you try and register an option that has already been registered by another module, a console error will be thrown

Functions

Table Function

To make a function available on the table object, you can use the registerTableFunction function.

This function takes two arguments, the first is the name of the table function, the second is the function that should be called when that function is called on the table..

this.registerTableFunction("hello", function(){
    console.log("Hey There")
});

This will then be callable on the table once it has been instatiated

table.hello();

Unique Name
Functions must have a unique name, if you try and register an function that has already been registered by another module, a console error will be thrown

Component Function

Functions can also be registered on any of the Component Objects used by the table, this can be done using the registerComponentFunction function.

This function takes three arguments, the fist is the type of component to register the function against. This can take one of five values:

  • row - The row component
  • column - The column component
  • cell - the cell component
  • group - the group component
  • calc - the column calc component

The second is the name of the function, the third is the function that should be called when that function is called on the table.

this.registerComponentFunction("cell", "hello", function(){
    console.log("Hey There")
});

This will then be callable on any matching component

cell.hello();

Unique Name
Functions must have a unique name on the component type it is registered on, if you try and register an function that has already been registered by another module, a console error will be thrown

Options

Once options have been registered during the construction of the module they can then be accessed by any module once the initialize function has been called.

Retreive Option Value

The value of any table option can be retrieved using the options function. This takes the key of the option as its first argument.

var option = this.options("selectableRows");

Nested Data
This function does not handle nested data, to retrieve data stored on nested objects, retrieve the top level object with this function and then navigate the structure as you would normally.

Update Option Value

You can update a table setup option at any point using the setOption function. The first argument should be the key of the option, the second should be the value

this.setOption("selectableRows", false);

It should be noted that changing an option will not automatically update the table to reflect that change, you will likely need to call the refreshData function to trigger the update.

Module Isolation
In order to keep modules isolated from one another it is important that you do not try and alter the options of one module from inside another.

Internal Events

Internal events are used by modules to send and receive data between themselves and the rest of Tabulator. The Event Bus Documentation contains a full list of internal events, and their different type and expected responses.

Module Isolation
Modules should never make direct function calls on one another, they should always communicate through the event bus. This keeps functionality isolated and allows for easy extension of the table

Event Naming

All events in Tabulator are dispatched with names that allow other modules to subscribe to them, by convention internal table event names should follow a kebab-case convention, with the first word in the event being the name of the module where it originated. For example if the menu module had an event for when a menu was opend it would be named:

menu-opened

Subscribe To An Event

There are hunderds of different internal events dispatched my the Tabulator core and the built-in modules, you can subscribe to any of these events using the subscribe function.

Any arguments passed into the dispatched event will be passed into the subscriber function, a list of arguments sent to each with each event can be found in the Event Bus Documentation.

this.subscribe("example-event", function(arg1, arg2, arg3){
    //do something
});

There are several different type of event that can be subscribed to, each ojne has different requirements on what the subscriber is expected to do. For more detals checkout the Event Dispatch Documentation, it contains examples of each type of dispatched event and how to correctly repspond to it.

Unsubscribe From An Event

Sometimes after subscribing to an event it may be nessisary to unsubscribe from that event to avoid receiving further events. You can do this using the unsubscribe function.

This function takes the name of the event as the first argument and the subscriber function as the second argument. If you are going to be unsubscribing from an event, you will need to store pointers to the orginal subscriber function in order to unsubscribe from it.

var subscriberFunc = function(arg1, arg2){
    //do something
}

//subscribe to an event
this.subscribe("example-event", subscriberFunc);

//unsubscribe from an event
this.unsubscribe("example-event", subscriberFunc);

Dispatching Events

Internal events can be dispatched in several ways depending on the type of response you want if any.

Dispatch

Dispatched events are simple send and forget notifications which do not return any values. Then can be triggered using the dispatch function.

The first argument is the name of the event, any other arguments after the event are then passed to the subscribers

this.dispatch("example-event", arg1, arg2, arg3);

The subscribing function will then receive the event like this:

this.subscribe("example-event", function(arg1, arg2, arg3){
    //do something
});

Example
An example usage case of this is when a row is deleted, the row dispatches a row-deleted event that lets modules know that a row has been deleted so they can carry out any needed housekeeping functions.

Chain

Chain events are used when you want to receive a response to an event, they can be used to request feedback from other modules to the result of an event.

Because any number of modules can be subscribed to an event, this isnt a simple call and response. Each subscriber is called in turn an passed the results of the last to subscriber and should return its own response combined with with that of the previous subscribers. When the last subscriber returns the result is returned to the dispatching module.

The first argument is the name of the event, The second argument is an array of arguments to be passed to the subscriber function. The third argument is the initial value to be passed into the previous value argument of the first subscriber. The fourth argument is the fallback value that will be returned if their are no subscribers to the event, this can either be a value or a callback function that is called in the event of no subscribers, that should return the fallback value,

var result = this.chain("example-event", [arg1, arg2, arg3], {}, {noparams:true})

Subscribers to a chain event will receive the arguments passed in the arg array followed by the returned response of the previous subscriber, or if they are the first subscriber the initial value passed into the chain function. The subscriber should then return the previous value amended with any changes the module wants to introduce.

this.subscribe("example-event", function(arg1, arg2, arg3, prevValue){
    prevValue.myProp = "value";
    return prevValue;
});

When the last subscriber returns, it is this return value that is returned from the chain function

Example
An example usage case of this when the data loader dispatches the data-params event to retrieve a list of ajax paramaters for a request. the filter, sort and pagination modules then add their paramters to the params object and pass it back to the data loader.

Confirm

The confirm event should be used to check if other modules are happy for something to proceed, if any module returns to to the event it will return true, otherwise it will return false.

The first argument is the name of the event, any other arguments after the event are then passed to the subscribers

var success = this.confirm("example-event", arg1, arg2, arg3);

The subscribing function will then receive the event like this and should return true if it wished to confirm the event:

this.subscribe("example-event", function(arg1, arg2, arg3){
    return true;
});

Example
The data loader dispatches a data-load confirm when it starts to load data, to allow modules to take control of the loading process and for example make an ajax request instead of the data being loaded from an array.

Subscription Check

By default Tabulator will check if any anyone has subscribed to an event before handling a dispatch event, so you can safely call any dispatch event without consideration for who has subscribed.

In some cases you may be doing a lot of work to generate the contents to pass to the dispatcher, and may want to check if anything is subscribed to the event before carrying out the work. To do this Tabulator provides a couple of helper functions

Subscription Check

The subscribed function can be used to check if anything is subscribed to an event. You should pass in the name of the event as the first argument and it will return true if there are any subscribers.

var isSubscribed = this.subscribed("example-event");

Subscription Change

You can register to be notified if the number of subscribers to a particular event have changed, using the subscriptionChange function.

This function takes the name of the event as the first argument. the second argument should be a callback function that is triggered when the number of subscribers to an event changes.

this.subscriptionChange("example-event", function(added){
    //added - true if a subscriber has been added, valse if they have been removed
});

The callback is passed one argument, a boolean that is true if a subscriber has been added and false if one has been removed.

The callback being passed a false value does not mean that the event has no subscribers, only that one has been removed. You can then use the subscribed function if you need to check if any subscribers are left.

External Events

External events are used by modules to trigger events on the table object that users can subscribe too. The Event Documentation lists all current external events.

Event Naming

All events in Tabulator are dispatched with names that allow other modules to subscribe to them, by convention internal table event names should follow a camelCase convention, with the first word in the event being the name of the module where it originated. For example if the menu module had an event for when a menu was opend it would be named:

menuOpened

Dispatching Events

Unlike internal events, external events can only be dispatched in one way. They are send and forget style events that do not take any return value from the event callback.

If you want users to be able to repsond to an event then you should be setting up a callback usint a Table Option.

External events can be triggered using the dispatchExternal function. The first argument is the name of the event, any other arguments after the event are then passed to the subscribers

this.dispatchExternal("exampleEvent", arg1, arg2, arg3);

Subscription Check

By default Tabulator will check if any anyone has subscribed to an event before handling a dispatch event, so you can safely call any dispatch event without consideration for who has subscribed.

In some cases you may be doing a lot of work to generate the contents to pass to the dispatcher, and may want to check if anything is subscribed to the event before carrying out the work. To do this Tabulator provides a couple of helper functions

Subscription Check

The subscribedExternal function can be used to check if anything is subscribed to an event. You should pass in the name of the event as the first argument and it will return true if there are any subscribers.

var isSubscribed = this.subscribedExternal("example-event");

Subscription Change

You can register to be notified if the number of subscribers to a particular event have changed, using the subscriptionChangeExternal function.

This function takes the name of the event as the first argument. the second argument should be a callback function that is triggered when the number of subscribers to an event changes.

this.subscriptionChangeExternal("example-event", function(added){
    //added - true if a subscriber has been added, valse if they have been removed
});

The callback is passed one argument, a boolean that is true if a subscriber has been added and false if one has been removed.

The callback being passed a false value does not mean that the event has no subscribers, only that one has been removed. You can then use the subscribedExternal function if you need to check if any subscribers are left.

Data & Display Handlers

Certain modules may want to alter the number of rows displayed in the table. For example, filtering out rows, sorting data, inserting group headers etc. In order to do this you need to register a handler function, which is called by the row management pipeline when the table is being redrawn.

When registering a pipeline, you pass two arguments into the registration function, the fist is the handler function which should be called when needed, the should accept an array of rows to be processed as its first argument and return an array of processed rows. The second argument of the registration function is the priority value for this handler, which denotes where in the pipeline it will be called, the higher the number the later in the pipeline it will be called.

this.registerDataHandler(function(rows){
	return rows.slice(0,10);
}, 30);

In the example above we have a data handler that takes all the table rows and returns the top 10. With a priority of 30 this is called after the rows have been filtered (because they both have a priority of < 30)

For an indepth description of the row management pipeline, and to find out the priorities of exsiting handlers, chekout the Lifecycle Documentation

Pipeline Phases

The row management pipeline is built in two distinct phases, that have their own priority lists. The section below will discuss the differences between the two phases had how to use them

As a general rule you should only register one handler per module.

Data Handlers

Data handlers are called in the first phase of the row management pipline, and these handler deal with manipulating rows in a way that alters the structure of the data of the table such a filtering and sorting.

These are always called when the underlying data of the table changes, but not when a display event such as chaning row grouping happens.

These can be registered using the registerDataHandler function

this.registerDataHandler(function(rows){
	return rows.slice(0,10);
}, 30);

Display Handlers

Display handlers are called in the second phase of the row management pipline, these handlers deal with manipulating the filter/sorted data and augmenting the resulting rows. For example the group module uses a display handler to inject row headers into the output, the tree module uses its handler to inject child rows.

These are called when the display of the table data has changed.

These can be registered using the registerDisplayHandler function

this.registerDisplayHandler(function(rows){
	return rows;
}, 30);

Refreshing & Redrawing Data

When someone triggers an action on your module, for example they might trigger a sort or change page, then your module needs to be able to tell the table to refresh the current data. To do this you can call the refreshData function.

this.refreshData();

The refresh data function will re-trigger the pipeline from the point of your modules hanlder registration, so for example if you registered it after the group module, it would not re run the group hanlder, but it would run your module on the original data from the group module, and then any other modules with a higher priority order. That way Tabulator is not needlesly reprocessing data that hasnt changed.

Refresh In Position

When you trigger a refresh of the data, the scroll position of the table will reset to the top, as this is the desired result in most usage cases.

If you would prefer that the table maintains its vertical scroll position when redrawing then you can pass a value of true to the first argument of the refreshData function

this.refreshData(true); //refresh data in position

Refresh Whole Pipeline

As mentioned above when you trigger a refesh it will run the pipeline from your handler onward. There may be some occasions where you need to rerun the whole piepline again or just rerender the existing data for some reason.

To do this you can pass a second argument to the refreshData function the specifies where in the pipeline to begin the refresh. This can take one of three values:

  • all - this will rerun the whole pipeline including all hanlders, both data and display
  • display - this will run all the display handlers again, but not the data handlers
  • end - this will not run any of the hanlders again but will trigger a redraw of the current display rows

this.refreshData(false, "display"); //refresh all displau handlers

Localization

If your module makes an changes that adds static text to the table ui (for example the pagination module adds buttons with "next" and "prev" text) then you should pull your text from the built in localization system to give users the option to localize their table.

There are several lang functions on a module to help retrieve set the appropriate localized text where needed.

Core Modules
Normaly we would strongly advise against adding functionality that calls directly on other modules because they may not be included by a user in their project. The exceptions to this rule are the lang, layout and comms modules which are considered core to the Tabulator project and included in all tables.

Lang Keys

Each of the lang text functions take the lang key as their first argument, this is a gey that denotes where in the lang values object the value is located. and is a pipe "|" seperated list of keys to navigate the nested object properties of the lang object

A full description of the lang values object can be found in the Localization Documentation.

In the case of the pagination "next" button for example the key would be pagination|next because it is the next property of the pagination object in the lang object:

{
    pagination:{
        next:"next",
    }
}

Default Values

If you are using the lang system, it is recomended that you add your default english language strings to the default lang object located in /src/js/modules/Localise/defaults/langs.js this will allow users to override the values before the initialize their table.

Lang Functions

Bind Value

In most cases, you are going to set the text on an element and then expect that text to change if the localization of the table changes.

To add text to an element that changes if the localization changes, you can use the langBind function. This function takes two arguments, the first is the lang key as described above, the second is a callback that should set the value on the element. This callback will be called as soon as you call the langBind function, and again any time the table locale changes.

For example if we have a button element assigned to a variable, let's call it "nextBut", we could then set its lable with the following code:

this.langBind("pagination|next", (value) => {
    nextBut.innerHTML = value;
});

Text Value

There are some instances where the localized text is only displayed for an instant, such as an ajax loading spinner. In these instances you simply want to get the currentl lang text the moment the element is added to the DOM.

You can do this using the langText function. This function takes the lang key as its first argument and returns the text value for the key

var label = this.langText("data|loading");

Current Locale

On occasion you may need to know the current locale of the table, for example locale based text sorting. You can use the langLocale function to retrieve the current language locale.

var locale = this.langLocale();

Inter-Table Communication

It can sometimes be nessisary to trigger functionality on one table from another (moving rows between tables is a great example of this). To acheive this you can use the built in comms functions.

Core Modules
Normaly we would strongly advise against adding functionality that calls directly on other modules because they may not be included by a user in their project. The exceptions to this rule are the lang, layout and comms modules which are considered core to the Tabulator project and included in all tables.

Connections List

Before you can send a message you need to retreive a list of connections. to do this you pass an array of table identifiers to the commsConnections function. This function will then return an array of connections that match the idenfiers provided.

The array of table identifiers should contain either CSS selector strings that identify the tables, DOM nodes for the tables, Tabulator instances, or a combination there of.

var connections = this.commsConnections(["#example-table", "#another-table"]);

If no matching tables are found the function will return false

Send Message

Sending a message to these connected tables can then be acheived using the commsSend function.

Sending a message, is actually the process of triggering a function on a module in the remote table. When calling the commsSend function, the first argument is a list of connections, retrieve with the commsConnections function, the second argument is the name of the module being connected too, the third argument is the function being called on that module, and the fourth argument is any data you want to pass to that function

this.commsSend(connectionList, "moveRow", "connect", {row:row})

In the example, a connection is established to each of the tables in the connection list and the connect function is called on the movable rows module and passed the data object.

Column Layout

It can sometimes be nessisary to interact with the column layout of the table when building a module. either by finding out the current layout mode of the table or to trigger a refresh of the column layout after something has been updated.

Core Modules
Normaly we would strongly advise against adding functionality that calls directly on other modules because they may not be included by a user in their project. The exceptions to this rule are the lang, layout and comms modules which are considered core to the Tabulator project and included in all tables.

Get Layout Mode

The layoutMode function will return the name of the current table column layout mode

var layout = this.layoutMode()

This will return a string with one of five possible values from the built in layout modes. If you have registered a custom layout mode then this value will also be vailable:

  • fitData
  • fitDataFill
  • fitDataTable
  • fitDataStretch
  • fitColumns

Refresh Layout

If you have altered the contents of a cell or resized a column or the table it may be nessisary to refresh the column layout to fit the new table size. You can do this using the layoutRefresh function.

this.layoutRefresh()