Create a custom refiner panel

Here, we will look at creating a new column refiner panel. This might be required if you need some columns to have different versions of the text, numeric or date panels; or if you need more sophisticated refinement; or if you need to support refining another data type.

1. Creating a panel variant

You are able to create a new panel to be used only for specific grid columns. To do this, simply repeat all the actions from step 3 of Configure column refiner panels, except for the last step where you override the component factory alias.

When you’ve created your new component factory, it will need a new alias for the renderer to use. Add the following to your aliasDefinitions.xml:

<alias name="caplinx.fxtrader.text-refine-component-alternate" defaultClass="caplinx.fxtrader.AlternateTextRefinerComponentFactory"></alias>

Now that you have an alternate component factory along with its own alias, you will need to create a new renderer to implement it:

<renderer type="caplinx.fxtrader.alternateTextRefiner">
    <control className="caplin.control.basic.TextControl">
        <handler className="caplin.element.handler.grid.GridColumnRefinerRendererHandler">
            <attribute name="component" value="caplinx.fxtrader.text-refine-component-alternate"/>
            <attribute name="filterType" value="WILDCARD"/>
        </handler>
    </control>
</renderer>

Finally, we update our gridDefinitions.xml so that one of our columns uses the new renderer:

<column id="blotter-fx-trade-id" fields="TradeID" displayName="@{caplinx.fxtrader.gridcolumnheaders.trade_id}" width="100" mandatory="true"
        primaryFieldType="number" headerRenderer="caplinx.fxtrader.alternateTextRefiner" cssClass="blotter-text"/>

2. Adding parsing and formatting to the filter input

By default, the refiner panels will attempt to use the same formatters that are used by the grid column’s cell renderer. This is not possible if the cell renderer is a composite renderer. It is also possible to declare parsers and formatters to be used in the filter input by specifying an upstream and/or downstream on the caplinx.fxtrader.alternateTextRefiner renderer:

<renderer type="caplinx.fxtrader.alternateTextRefiner">
    <control className="caplin.control.basic.TextControl">
        <handler className="caplin.element.handler.grid.GridColumnRefinerRendererHandler">
            <attribute name="component" value="caplinx.fxtrader.text-refine-component-alternate"/>
            <attribute name="filterType" value="WILDCARD"/>
        </handler>
    </control>
    <upstream>
        <transform className="caplin.element.parser.RegExpParser">
            <attribute name="match" value="foo" />
            <attribute name="replace" value="bar" />
        </transform>
    </upstream>
    <downstream>
        <transform className="caplin.element.formatter.TrimFormatter" />
    </downstream>
</renderer>

The text refiner panel created by this renderer would have its filter input parsed by a regular expression and formatted to trim white space. This is purely an illustrative example of parsing and formatting. An example of where this configuration is more important is for parsing and formatting dates in date refiner panels.

3. Developing a new refiner panel using Presenter

The text, numeric and date refiner panels are all implemented in Presenter and some pieces can be reused if a new panel variant is required. Say your application needed a currency pair filtering panel which would offer the user a dropdown containing supported currency pairs, and the ability to easily select the ones they want to see in the grid. Firstly, a RefinerPresentationNode is required. The outline of a CurrencyPairFilteringPresentationNode is shown below:

var RefinerPresentationNode = require('caplin/grid/refine/component/presenter/RefinerPresentationNode');
var topiarist = require('topiarist');
var emitr = require('emitr');

function CurrencyPairFilteringPresentationNode(templateName) {
    RefinerPresentationNode.call(this, templateName);
    // The node should listen for changes to its properties and emit two events:
    // 'validation-error' when the node enters an invalid state and
    // 'validation-success' when the node enters a valid state
    // These inform the GenericRefinerPresentationModel to disable/enable the 'Apply' button
}
topiarist.extend(CurrencyPairFilteringPresentationNode, RefinerPresentationNode);
emitr.mixInto(CurrencyPairFilteringPresentationNode);

CurrencyPairFilteringPresentationNode.prototype.setColumn = function(column) {
    RefinerPresentationNode.prototype.setColumn.call(this, column);
    // Initialise the node with the filter state of the column
};

CurrencyPairFilteringPresentationNode.prototype.apply = function() {
    // Return false if the model state is invalid
    // Apply the node state as filters on the column using GridColumn#addFilter
};

CurrencyPairFilteringPresentationNode.prototype.clear = function() {
    // Clear the filters on the column using GridColumn#clearFilters
    // Reset the state of the node if necessary
};

caplinx.fxtrader.CurrencyPairFilteringPresentationNode = CurrencyPairFilteringPresentationNode;

This class can also implement onOpen and/or onClose methods if it needs to do any initialisation or clean up. We also need a factory to build the component. Here, we can use the GenericRefinerPresentationModel, which is composed of a default node to handle sorting and the new CurrencyPairFilteringPresentationNode to handle filtering. A corresponding HTML template for the filtering node also needs to be created.

var ComponentFactory = require('caplin/component/factory/ComponentFactory');
var GenericRefinerPresentationModel = require('caplin/grid/refine/component/presenter/GenericRefinerPresentationModel');
var DefaultSortingPresentationNode = require('caplin/grid/refine/component/presenter/DefaultSortingPresentationNode');
var CurrencyPairFilteringPresentationNode = require('caplinx/fxtrader/CurrencyPairFilteringPresentationNode');
var GridColumnRefinerPresenterComponent = require('caplin/grid/refine/component/GridColumnRefinerPresenterComponent');
var topiarist = require('topiarist');

function CurrencyPairRefinerComponentFactory() {}
topiarist.implement(CurrencyPairRefinerComponentFactory, ComponentFactory);

CurrencyPairRefinerComponentFactory.prototype.createFromXml = function(xml) {
    var sortingNode = new DefaultSortingPresentationNode('caplin.grid.refine.default-sorting');
    var filteringNode = new CurrencyPairFilteringPresentationNode('currency-pair-template-id');
    var presentationModel = new GenericRefinerPresentationModel({
        sort: sortingNode,
        filter: filteringNode
    });

    return new GridColumnRefinerPresenterComponent('caplin.grid.refine.refine-panel', presentationModel);
};

caplinx.fxtrader.CurrencyPairRefinerComponentFactory = CurrencyPairRefinerComponentFactory;

Finally, we need an alias for the component factory, and a renderer to make use of it. The new renderer - caplinx.fxtrader.currencyPairRefiner - can then be used as the headerRenderer on the relevant grid columns.

<alias name="caplinx.fxtrader.currency-pair-refine-component" defaultClass="caplinx.fxtrader.CurrencyPairRefinerComponentFactory"></alias>
<renderer type="caplinx.fxtrader.currencyPairRefiner">
    <control className="caplin.control.basic.TextControl">
        <handler className="caplin.element.handler.grid.GridColumnRefinerRendererHandler">
            <attribute name="component" value="caplinx.fxtrader.currency-pair-refine-component"/>
        </handler>
    </control>
</renderer>

4. Developing a new refiner panel without using Presenter

It is possible to implement new panel variants without using Presenter. In this case, the creation of factories, aliases and renderers stays the same. Each factory is expected to return a component that implements the caplin/grid/refine/GridColumnRefiner interface. This allows the click handler to provide the component with a GridColumn which it can use to get and set refinement state. The component will also be need to implement the caplin/component/Component and caplin/component/ComponentLifecycleEvents interfaces. Lastly, the component should emit a GridColumnRefinerEvents.REFINER_ACTION_COMPLETE event to inform the renderer that the panel should be closed and disposed of.