Skip to content

Documentation - Extend Smart Table

There are many discussions on the benefits to write modular code in Javascript and how to do it (I recommend the essays of Reginald Braithwaite on the topics). This is a very tricky subject although Javascript is a very powerful language when it comes to create structural or behavioral design patterns.

Smart table tries to make the developer feel comfortable by using the idioms of the language only. You should be able to extend and compose smart table code as you would do for any regular function or objects: this is Javascript !

This section aims at giving you an hint on how smart table fits in several well known composition mechanisms.

Composition

In this example we will take the table directive factory to create a new factory with more capabilities.

An important part of Oriented Object Programming is encapsulation (the ability to "hide" instance private data). On the other hand, we will probably want some data such the table state and the data set to be shared across different behaviors so we can build more sophisticated structures.

This example will share references to critical data among smart table extensions while hiding it to the outside world (thanks to a closure)

import {smartTable} from 'smart-table-core';

const defaultTableState = {sort: {}, filter: {}, search: {}, slice: {page: 1}};
//an extra (useless) behavior
const smartTableExtension = function ({table, tableState, data}) {
    return {
        resetAndRemoveFirst() {
            //example: modify the shared instance of tableState
            Object.assign(tableState, defaultTableState);
            //example: use core api (emitting an event here, and calling a refresh)
            table.dispatch('TABLE_RESET');
            //example: use data collection reference (here remove first item)
            data.splice(0, 1);

            table.exec();
        }
    };
};

// our composed factory
const superSmartTable = function ({data, tableState = defaultTableState}) {
    const core = smartTable({data, tableState});
    return Object.assign({}, core, smartTableExtension({data, tableState, table: core})); //compose
};

//use our super smart table
// your data
const data = [
    {surname: 'Deubaze', name: 'Raymond'},
    {surname: 'Foo', name: 'Bar'},
    {surname: 'Doe', name: 'John'}
];

const table = superSmartTable({
    data,
    tableState: {sort: {pointer: 'surname'}, filter: {}, search: {}, slice: {page: 1}}
});

// core methods available (here print the displayed data)
table.onDisplayChange(items => console.table(items.map(i => i.value)));

//new methods available as well
table.resetAndRemoveFirst();



Mixin

Here, we will borrow some object behaviors to replace the default ones provided by the core directive.

import {smartTable} from 'smart-table-core';

//change the structure of returned items
const smartTableExtension = function ({table, tableState, data}) {

    const oldChangeRegister = table.onDisplayChange;

    //will overwrite the default onDisplayChange
    return {
        onDisplayChange(listener) {
            oldChangeRegister(function (items) {
                const itemValues = items.map(i => i.value);
                listener(itemValues);
            });
        }
    };
};

// our composed factory
const superSmartTable = function ({data}) {
    const core = smartTable({data});
    return Object.assign(core, smartTableExtension({table: core})); //overwrite core method by mixin the extension within the core
};

//use our super smart table
const data = [
    {surname: 'Deubaze', name: 'Raymond'},
    {surname: 'Foo', name: 'Bar'},
    {surname: 'Doe', name: 'John'}
];

const table = superSmartTable({data});

// core methods available
table.onDisplayChange(items => console.log(items)); // no need to extract "value" property as the method has been overwritten

table.exec();

Decoration

This example will simply add operations (or "advices") like logging function calls around common table functions.

import {smartTable} from 'smart-table-core';

const loggerAdvice = (...fns) => object => {
    const output = Object.assign({}, object);
    const methods = Object.keys(object).filter(key => typeof object[key] === 'function' && fns.includes(key));
    for (let method of methods) {
        output[method] = (...args) => {
            console.log(`${method} called`);
            return object[method](...args);
        };
    }
    return output;
};

const logCoreMethods = loggerAdvice('sort', 'filter', 'search', 'slice');

const smartTableExtension = function ({table}) {
    return logCoreMethods(table);
};

// our composed factory
const superSmartTable = function ({data}) {
    const core = smartTable({data});
    return smartTableExtension({table: core});
};

//use our super smart table
const data = [
    {surname: 'Deubaze', name: 'Raymond'},
    {surname: 'Foo', name: 'Bar'},
    {surname: 'Doe', name: 'John'}
];

const table = superSmartTable({data});

table.sort({pointer: 'name'});
// > 'sort called'

Adapters

This example use the low level smart table API to define a new API perhaps more familiar or friendlier to your habits (using promises instead of events for example).

There are different ways of doing it:

  1. You can discard totally the built in event system and use the eval method but you'll have to manage table state mutation yourself
  2. You can use event system to create new methods (or overwrite core ones)
import {smartTable} from 'smart-table-core';

const smartTableExtension = function ({table, tableState}) {
    return {
        // using eval
        sortAndReturn(sortState) {
            Object.assign(tableState, {sort: sortState}); //you have to mutate table state yourself
            return table.eval();
        },
        //using event system (note you should handle error as well)
        sliceAndReturn(sliceState) {
            return new Promise(function (resolve, reject) {
                //register once
                const listener = function (items) {
                    resolve(items);
                    table.off('DISPLAY_CHANGED', listener);
                };
                table.onDisplayChange(listener);
                table.slice(sliceState);
            });
        }
    };
};

// our composed factory
const superSmartTable = function ({data, tableState = {sort: {}, filter: {}, search: {}, slice: {page: 1}}}) {
    const core = smartTable({data, tableState});
    return smartTableExtension({table: core, tableState});
};

const data = [
    {surname: 'Deubaze', name: 'Raymond'},
    {surname: 'Foo', name: 'Bar'},
    {surname: 'Doe', name: 'John'}
];

const table = superSmartTable({data});

table.sortAndReturn({pointer: 'name'})
    .then(items => {
        console.log(items.map(i => i.value));
        return table.sliceAndReturn({page: 2, size: 1});
    })
    .then(items => {
        console.log(items.map(i => i.value));
    });

Extension directives

The table directive factory proposes a contract to avoid the repetition of the code related to the composition mechanism. You can pass has many factories you want: they will have to return an extension to be merged with the core table and will receive as arguments references to the data collection, the tableState and the core table instance.

import {smartTable} from 'smart-table-core';

const extension1 = function ({tableState, data, table}) {
    return {
        greet() {
            console.log('hello');
        }
    };
};

const extension2 = function ({tableState, data, table}) {
    return {
        foo() {
            //...
        }
    };
};

const data = [
    {surname: 'Deubaze', name: 'Raymond'},
    {surname: 'Foo', name: 'Bar'},
    {surname: 'Doe', name: 'John'}
];

const table = smartTable({data}, extension1, extension2);

table.greet();
table.foo();

Example wit existing plugins

There are already a handful of community extensions you can refer to in order to get more examples