FXB Grid

1Overview of the FXB grid

The FXB grid has the following key features:

Handles large numbers of rows (into the millions) with no significant impact on performance.

Handles real-time data updates such as market prices with minimal processor load - and therefore minimal battery usage on mobile.

No dependencies - no requirement for jQuery etc. The grid consists of one Javascript file and one CSS file. Combined Javascript and CSS size is < 200KB.

Automatically resizes to its container, and can have rules for responsively changing which columns are displayed, and their sizes, based on the width of the grid container. Can also responsively change row height based on page font size.

Easy to re-style using CSS, and easy to override rendering using Javascript.

Allows column sorting, resizing, and moving (using drag-and-drop)

Columns can be fixed to the left and/or right of the grid

Allows row moving (using drag-and-drop)

Selection of rows, cells, and cell-ranges, with Ctrl+C or programmatic copying to the clipboard

Can have column footers showing totals, averages etc, or custom content such as action buttons in the footer

Provides simple but powerful filtering/searching

Allows grouping of records

Full support for touch events

Full right-to-left support

Full background transparency; effects such as merged cells and fixed columns don't require parts of the grid to have opaque backgrounds.

The example files included with the grid also demonstrate some other advanced features:

Frozen rows

Merged cells, both horizontally and vertically

Cells occupying the whole row width

Individual/variable row height

Drag and drop between grids

Synchronizing two (or more) grids

Parent & child rows

State persistence

1.1Design philosophy - mobile-friendly; minimal dependencies and overlap

The FXB grid's design philosophy is different from many other grids. It concentrates on providing an ultra-fast and mobile-friendly grid, and it aims to minimise dependencies, complications, and overlaps with other code and design.

A key example is that the grid does not have any built-in functionality for loading data from a server. You give the grid a simple Javascript array of records. How you collect that data is entirely up to you, and the grid does not lock you into some proprietary data object model or server-request framework. As a result, the grid does not have built-in support for paging, but you can easily add this - or infinite scroll / lazy loading - yourself, as illustrated by examples which are included with the grid.

Similarly, the FXB grid does not have built-in cell editing because this could raise all sorts of conflicts with other UI styling in your project, plus issues around input and parsing of values, localization, and optimising the UI differently on mobile and desktop. There are examples of extending the grid to handle cell editing below and in the example files which are included with the grid.

The grid aims to be fast in development as well as in its performance. In particular, it wants you to avoid wasting hours and $100s tearing out functionality and design which is inconsistent with the rest of your project before you start building what you actually want.

The grid's standard CSS does deliberately minimal styling (though its "flat" look is currently quite fashionable). Rather than giving you a range of themes none of which matches your project, the aim is to let you build up what you do want rather than wasting time disabling standard styling which you don't want.

Finally, it's a grid, not a spreadsheet. It's not trying to be Excel in a web browser. The FXB grid looks as much towards mobile, where spreadsheet ambitions would be impractical because of limited screen space, as it does towards the desktop.

2The basics

The grid consists of one CSS file (supplied in both full and minified form) and one Javascript file. You simply need to reference these files in your page:

<link rel="stylesheet" type="text/css" href="your-path-of-choice/fxb.grid.min.css"/>

<script src="your-path-of-choice/fxb.grid.min.js"></script>

You create a grid by having a container element for it, such as <div id="MyGrid"></div>, and creating an instance of the FXB.ui.Grid class. You pass configuration information to the class constructor, and this must contain three compulsory things:

The container

A description of the grid columns

The data for the grid, as a simple array

For example:

var columns = [

{id: "firstname", text: "First Name"},

{id: "lastname", text: "Last Name"},

{id: "spacer", spacer: true} // Columns can have IDs which don't exist in the data

];

var data = [

// Data can contain properties which don't exist in the list of columns.

// Each row can also contain metadata

{firstname: "Wendy", lastname: "Brown", age: 47},

{firstname: "John", lastname: "Smith", age: 23, __selected: true},

{firstname: "Harriet", lastname: "West", age: 33}

];

new FXB.ui.Grid({

container: "MyGrid", // Can either be the HTML element itself, or its id attribute

columns: columns,

data: data

});

The grid will not modify your outer container element in any way, but it will remove and replace any existing content which the container has.

Unlike the simple example above… except on very large datasets, it is strongly recommended that each row of data contains a unique identifier value, and that you tell the grid what this is by specifying a rowKey in the grid's configuration. This is not compulsory, but you cannot use some grid functionality such as UpdateCell() unless there is a unique row key. The rowKey doesn't have to be a column in the grid:

new FXB.ui.Grid({

container: "MyGrid",

columns: [

{id: "firstname", text: "First Name"},

{id: "lastname", text: "Last Name"}

],

rowKey: "customerId",

data: [

{customerId: 17, firstname: "Wendy", lastname: "Brown"},

{customerId: 1042, firstname: "John", lastname: "Smith"},

{customerId: 96, firstname: "Harriet", lastname: "West"}

]

});

2.1Loading and updating data

You present data to the grid as a simple array, like the examples above. You are responsible for handling any data downloads from a server. This also means that you are responsible for handling paging, if required.

You can load new data into the grid using its Rebind() method. This simply takes a complete new array of data as its parameter. For example:

mygrid.Rebind([

{presidentId: 44, firstname: "Barack", lastname: "Obama", age: 57, hair: "black"},

{presidentId: 45, firstname: "Donald", lastname: "Trump", age: 72, hair: "orange"}

]);

By default the grid clones any data which you pass to it - note: a clone, rather than an array slice() - instead of holding references to your original data. This means that any functions in the data will be stripped out, and the data must not contain circular references because these will cause the cloning to fail. You can change this behaviour by setting noClone:true in the grid's configuration, potentially allowing some powerful effects.

A Rebind() can be a relatively costly operation if the dataset is large. You can more efficiently update individual cells in the grid using UpdateCell(). The parameters are the row's unique rowKey, the property to alter, and its new value:

mygrid.UpdateCell(45, "age", 73);

You can also update multiple values at once by passing a new row object to UpdateRow(). The new data must include the unique row key, but does not need to repeat any values which are not changing. For example:

mygrid.UpdateRow({

presidentId: 44,

age: 58,

hair: "grey"

});

If you are making several updates to cells or rows, then there is an optional extra parameter for UpdateCell() and UpdateRow() which tells the grid to leave filtering and sorting in a dirty state until you subsequently call Refresh().

Data updates should generally be very fast. For example, you can implement a stock market quote board, updating constantly, while consuming < 1% processor time on a typical device.

2.1.1Handling a no-data message

The grid does not have a standard no-data message because this would create a dependency on a translation file.

If there is no visible data (i.e. no data at all, or a filter condition which excludes all data) then the grid creates an empty container element with the class .fxbgrid-no-data. You can put your own message into this by implementing a noDataRenderer. For example:

new FXB.ui.Grid({

noDataRenderer: function(context) {

// The context.element is the empty no-data container created by the grid.

// You can put any content into this. The container's standard CSS definition

// gives it horizontal and vertical centering.

context.element.innerHTML = "No data";

}

});

If you want to vary the message based on whether data has been filtered out or whether there is no source data at all, you can inspect grid properties such isFiltered and rawDataRowCount.

2.1.2Large datasets

The grid is supplied with an example of loading 1,000,000 rows of data into it. This should perform well in all browsers.

However, there are two pieces of configuration which speed up the grid when dealing with these large datasets:

Turn off data cloning using noClone:true. This can have a substantial effect.

Omit a rowKey from the configuration, unless you need functionality such as UpdateCell() which requires the rowKey. This has a more minor effect.

Note: speed of sorting in large grids is mostly a data/browser operation rather than a time cost which is internal to the grid itself. The time is mostly incurred in the browser's Javascript engine swapping items in an array. Speed of sort will be substantially determined by how disordered the data is and the number of swaps which need to happen. For example, inverting the order on an already-sorted column will be much quicker than sorting on a new column.

2.2Defining columns

Defining the columns for the grid is a broad subject because columns can have a large number of configuration properties. This section is a relatively brief introduction. There is a full reference below. As well as the single array of columns[] described in this overview, you can also use a list of different responsive column sets for different container widths.

It's recommended that every column has an id, even if that column is something special such as a spacer which has no corresponding property in the data rows. For example:

columns: [

{id: "firstname", text: "First Name", width: 100},

{id: "lastname", text: "Last Name", width: "30%"},

{id: "dummyspacer", spacer: true} // No corresponding property in each data row

],

data: [

{firstname: "Wendy", lastname: "Brown"}

]

Columns can have a width, which can either be a number of pixels or a percentage of the total container width. If the width is a percentage, then there can also be minWidth and/or maxWidth values specifying limits in pixels for the translated size. A column can also have an onGetWidth() event handler which you can use to do custom calculations whenever the grid size changes.

Columns can be fixed to the left and/or to the right of the grid. For example:

columns: [

{id: "customerId", text: "ID", fixed: true}, // Fixed, to left because before unfixed columns

{id: "firstname", text: "First name"}, // Unfixed

{id: "lastname", text: "Last name"}, // Unfixed

{id: "actionicon", align: "center", fixed: true} // Fixed, to right because after unfixed columns

]

There must be at least one unfixed column. If all columns are marked as fixed, then no columns will be fixed.

Columns do not necessarily take up the full width of the grid. For example, if the combined columns are 400 pixels wide and the grid is 600 pixels wide, there will be blank space to the right of each row. As well as specifying widths as percentages adding up to 100%, you can also define one column as fill:true. It will then expand to consume any unused space. You can alternatively have one column - usually an empty column with no header text or data - which is defined as spacer:true. This column also expands to fill any unused space but, unlike fill:true, is hidden if not needed (i.e. if the other columns are wide enough to fill the whole grid).

The grid has limited built-in formatting options for cell values. If necessary, you can take full control of the display of data for a column by providing a cellRenderer function for it. Some simple examples:

columns: [

{id: "age", text: "Age", format: {type: "number", digits: 0}},

{id: "cashBalance", text: "Balance", format: {type: "cash", digits: 2}},

{id: "dob", text: "Date of birth", format: {type: "dateonly"}},

{id: "heightInInches", text: "Height", cellRenderer: RenderPersonHeight}

]

function RenderPersonHeight(context)

{

// Render height in inches as X'Y, e.g. 67 inches as 5'7

context.element.innerHTML = Math.floor(context.value / 12) + "'" + (context.value % 12);

}

The cellRenderer also provides a way of implementing things such as an action icon for each row in the grid.

You can have columns which refer to "virtual" data - derived values which do not physically exist in the source data, such as combining a first name and last name into a single name column. The complexity of this depends on which grid features you want to use.

2.3Enabling filtering

The FXB grid provides simple but powerful built-in filtering/searching. It can filter on numbers, text, or a single selection from a drop-down list. It does not provide date filtering or a multi-select list because these would create a dependency on a custom HTML control. You can add your own filter controls for any data types not handled natively.

You set up filtering on a column by defining a filter property. For example:

columns: [

{id: "firstname", text: "First name", filter: "textstart"},

{id: "lastname", text: "Last name", filter: "textstart"},

{id: "age", text: "Age", filter: "number"},

{id: "status", text: "Age", filter: "list", filterOptions: [

{value: "active", text: "Active"},

{value: "inactive", text: "Inactive"}

]},

{id: "notes", text: "Notes", filter: "textcontains"}

]

Text and numeric filtering are handled by presenting the user with a normal HTML input field. Both these types of filter share some common rules:

Users can type in multiple options (combined on an "or" basis), separated by commas or semi-colons.

Users can start a filter condition with ! to mean "not matching the following"

For example (assuming a text-start filter):

User input

Meaning

john

Matches any row where the column's value starts with "john"

!john

Matches any row where the column's value does not start with "john"

john,wendy,chris

Matches any row where the column's value starts with "john", "wendy", or "chris"

!john,wendy,chris

Matches any row where the column's value starts with none of "john", "wendy", or "chris"

Text-contains filters have two further special properties:

User input

Meaning

we br

Spaces are treated as word delimiters; matches "Wendy Brown" or "Wesley Bright"

nato

Allowed to match the initial letters of separate words in the comparison; matches "North Atlantic Treaty Organisation"

Number fields have further options:

User input

Meaning

>=N

Matches any row where the column's value is greater or equal to N

>N

Matches any row where the column's value is greater than N

<=N

Matches any row where the column's value is less or equal to N

<N

Matches any row where the column's value is less than N

N-M

Matches any row where the column's value is between N and M

'N

Treats the data as textual rather than numeric, and matches any row where the textual representation of the number starts with N. For example, '123 matches both 123 and 12345678

Combining these various options, it means that a user can simply but powerfully enter a numeric filter such as !<5,17 meaning "any value which isn't less than 5, and isn't 17".

2.4Displaying summary values or actions

You can display a summary value for a column simply by defining a footer for it. For example:

columns: [

{id: "name", text: "Customer name", footer: "count"}

{id: "name", text: "Age", footer: "avg"},

{id: "name", text: "Purchases", footer: "sum"}

]

You can use a footerRenderer for a column in order to display a custom summary calculation, or in order to re-use the summary row for a completely different purpose such as displaying an action button etc.

For example, you can display a count of the non-null values in a column as follows:

columns: [ {id: "emailaddress", footerRenderer: CountNonNull} ]

function CountNonNull(context)

{

// Need to loop through the context.data, performing our bespoke summary operation

var ct = 0;

for (var i = 0; i < context.data.length; i++) {

var R = context.data[i];

// Ignore any group header rows; only look at data rows

if (!R.__groupHeader) {

// See if the value for this column is non-null

if (R[context.column.id] != null) ct++;

}

}

// Put the count into the summary cell

context.element.innerHTML = ct;

}

To display some sort of action button or link in the summary row, you can simply use a function such as the following:

columns: [ {id: "emailaddress", footerRenderer: ShowFooterAction} ]

function ShowFooterAction (context)

{

// Create a button

var b = document.createElement("BUTTON");

b.innerHTML = "Action";

// Store a reference to the column ID

b.columnId = context.column.id;

// Add an event listener

b.addEventListener("click", function(e) {

// … Column ID is available via e.currentTarget.columnId

});

// Add the button into the footer element

context.element.appendChild(b);

}

2.5Grouping

You can let the user group the grid on a column by specifying canGroup:true in the column's definition. You can also set initial grouping on the grid by setting the grouping property in the grid configuration. Each group is expanded by default, but you can change this using the grid's collapseGroupsByDefault property.

You can change the grouping programmatically using the SetGrouping() method, and you can also programmatically expand and contract groups using ExpandGroup() and CollapseGroup().

You can display a summary row for each group - e.g. column totals, averages etc - by handling the onCalculateGroupSummary event. For example, the following function would add a summary row showing the average age of people in each group:

columns: [

{id: "name", text: "Name"},

{id: "country", text: "Country", canGroup: true},

{id: "age", text: "Age"},

],

onCalculateGroupSummary: function(context) {

// Don't display a summary if the group is collapsed

if (context.groupCollapsed) return null;

// Calculate total of age, from the groupRows[] in the context information

var totalAge = 0;

for (var i = 0; i < context.groupRows.length; i++) {

totalAge += context.groupRows[i].age;

}

// Turn on a summary for the group by returning a row of data to use as the summary

return {

name: context.groupRows.length + " people",

age: totalAge / context.groupRows.length // Average age

};

}

2.6Listening to grid events and rendering

You can hook into a wide range of grid events and rendering, using configuration properties for the grid such as onCellClick or a column's cellRenderer. These hooks let you respond to browser events such as clicks, override standard grid functionality, and change the HTML of the grid. For example:

new FXB.ui.Grid({

onCellClick: function(context) { … },

columns: [

{id: "customerId", cellRenderer: MyRendererFunction}

]

});

You can also assign handlers for events such as onCellClick after a grid has been created. For example:

var grid = new FXB.ui.Grid({ … });

grid.onCellClick = MyCellClickHandler;

grid.onHeaderClick = function (context) { … }

Some events require your handler function to return true, or else the standard grid functionality is prevented. For example, if you handle onHeaderClick then returning false (or null) prevents the click from setting/changing any sorting on the column.

You can use the grid's events to change grid rendering in two ways:

By defining a rendering function for a column, such as cellRenderer. This replaces the grid's standard HTML generation.

By handling a post-rendering event in the grid, such as onDataCellRender. This lets you modify the grid's HTML (rather than replacing it), and gives you the opportunity to attach extra event handlers to the HTML.

2.6.1Firing events on the container element

You can also choose to have all grid events fired as browser events on your container element, by turning on fireContainerEvents in the grid's configuration. For example:

// Get the container HTML element for the grid

var gridContainer = document.getElementById("MyContainerElement");

// Create a grid, asking for browser events

var grid = new FXB.ui.Grid({

container: gridContainer,

fireContainerEvents: true,

});

// Add a listener for cell clicks, via the browser event rather than handling onCellClick in the grid

gridContainer.addEventListener("cellClick", function(e) {

// The grid's context object for the event is in e.detail.

// Because the original trigger is a click, the original browser ClickEvent will

// be in e.detail.event

});

Notes:

There may be a small performance penalty in using browser events compared to hooking the grid's own internal notifications - see below.

This route lets you have multiple handlers for each event. You can use addEventListener to define multiple callbacks for each browser event, whereas you can only assign a single handler to a hook such as onCellClick.

The context object for the grid event is contained in the detail of the browser event.

Therefore, if the trigger for the event is something like a click, then the original browser event object such as a ClickEvent is in detail.event.

You cannot prevent standard grid functionality via this route. For example, handling the grid's onHeaderClick blocks the usual sorting functionality if you return false from your handler function, whereas you cannot block grid functionality by handling equivalent browser events.

Using browser events may create a (very) small performance penalty. When using its own internal notifications, the grid can issue only those for which you have defined a handler. When firing browser events the grid does not know if you have used addEventListener, and therefore has to issue all possible notifications in case a listener exists. If the grid contains a large number of visible cells, this can lead to a large number of events being fired.

You can mitigate this using alternative syntax for fireContainerEvents. If it is an array rather than a boolean, the grid will only issue browser events for items named in the array. For example:

var grid = new FXB.ui.Grid({

fireContainerEvents: ["cellClick", "cellContextMenu"],

});

container.addEventListener("cellClick", function(e) {

// Will be triggered, because listed in fireContainerEvents[]

});

container.addEventListener("rowClick", function(e) {

// Will not be triggered, because not listed in fireContainerEvents[]

});

2.7Special touch gestures

The grid has full support for touch devices, and provides three special touch gestures:

A long tap (or a long mouse hold) groups the grid by that column, if applicable - enlarging the area which the user can hit compared to the grouping bar at the bottom of the column header. The required duration of the tap can be configured using headerLongTapMS.

A three-fingered swipe up/down scrolls the grid to the top/bottom.

If multi-column sorting is turned on, a two-fingered tap on a column header adds the column to the existing sort (or changes its direction if already sorted).

In addition, you can define a handler for the grid's onCellSwipe event. This provides a simple alternative to having to set up your own touch/mouse listeners in order to detect and handle swipe gestures. For example, the edit-mode demo which is included with the grid handles swipe-left as a gesture for deleting rows from the grid.

3.1Element heights and widths

Element heights and widths can be controlled in two ways:

Using Javascript, by setting grid configuration properties such as headerHeight or rowHeight

Using CSS, by changing or overriding the classes such as such as .fxbgrid-default-rowHeight which define the grid's default height and width settings

Note: classes such as .fxbgrid-default-rowHeight are not actually applied to any grid elements. The grid reads their heights/widths from the CSS and uses them as defaults.

You can give each row an individual height using a __height value in the metadata, but you must also then turn on the hasIndividualRowHeights grid property.

Heights and widths can also be defined responsively, as a multiple of the font size applying to the grid's container.

3.2Modifying the grid CSS

The grid's standard CSS file is provided both in raw form, for reference and for modification, and also in minified form. You can change the standard CSS by modifying the file, or by overriding the standard CSS with later re-declarations of the same selectors.

Note: the grid's standard colours are (re-)defined in a separate block at the end of the file, providing a single concise place where you can change/override the standard colours.

To change the styles for a specific single grid only, the most efficient route is to give your container an ID attribute, and then to add selectors which refer to that ID. For example:

<div id="MyContainer">

<!-- Grid is inserted here -->

</div>

#MyContainer .fxbgrid-headers {

background: #000080;

color: white;

}

This overrides the header colour for the specific grid because a # selector has higher precedence than the class-based selectors in the standard CSS.

3.3Changing the style for a specific column

Columns have four sections: header, filter, data, and footer. You can style a specific column by providing a custom CSS class for a part of it in the column's configuration. For example:

columns: [

{id: "quantity", text: "Quantity", cssDataClass: "QuantityCells"}

]

/* Makes the column's data cells bold (but not the column's header or footer */

.QuantityCells .fxbgrid-data-cell { font-weight: bold; }

You can apply styling to an entire column by using its cssAddClass configuration. The following example makes all parts of the column red and bold:

columns: [

{id: "quantity", text: "Quantity", cssAddClass: "Red Bold"}

]

.Bold { font-weight: bold; }

.Red { color: red; }

You can also style columns based on their id property, without needing to define additional classes via cssAddClass etc. All cells in a column have a class name which is derived from the column's id, such as the following where there is a quantity column:

<div class="fxbgrid-header-cell fxbgrid-header-cell-quantity">

<div class="fxbgrid-data-cell fxbgrid-data-cell-quantity">

Therefore, you can also style a column using CSS such as the following:

.fxbgrid-header-cell-quantity, .fxbgrid-data-cell-quantity { font-weight: bold; }

3.4Changing the styling for specific rows and cells using CSS

You can change the styling for a specific row by setting __cssClass in the metadata for the row. For example:

.Bold {font-weight: bold;}

var data = [

{id: 23, name: "Susan", age: 42},

{id: 37, name: "Caroline", age: 71, __cssClass: "Bold"}, // This row will be bold

{id: 23, name: "Helen", age: 28}

];

You can style individual cells by using the combination of cssDataClass for the column and __cssClass for the row. Adding to the above example:

var columns = [

{id: "name", text: "Name"}, {id: "age", text: "Age", cssDataClass: "AgeColumn"}

];

var data = [

{id: 23, name: "Susan", age: 42},

{id: 37, name: "Caroline", age: 71, __cssClass: "BoldAge"}, // Makes the age column bold

{id: 23, name: "Helen", age: 28}

];

/* Only applied to the age column, and only if the data row has .BoldAge */

.BoldAge .AgeColumn .fxbgrid-data-cell-inner { font-weight: bold; }

3.5Changing the styling for specific rows and cells using post-rendering

Rather than using CSS as in the example above, it may sometimes be easier to do styling of individual cells (or rows) by doing "post-rendering" modification of the HTML which the grid creates. Instead of defining CSS classes in the column configuration and in the data, as in the example above, it may be simpler to do something like the following where onDataCellRender modifies a cell after creation based on the data in it:

new FXB.ui.Grid({

onDataCellRender: function(context) {

// Post-rendering of a cell. Make it bold if it's the age column

// and its value exceeds 70

if (context.column.id == "age" && context.row.age > 70) {

context.element.style.fontWeight = "bold";

// … or something like context.element.className += " Bold";

}

}

});

This method using onDataCellRender can also be used to style entire rows, by styling each cell in the row, but it may be more efficient to handle onRowCreate:

new FXB.ui.Grid({

onRowCreate: function (context) {

if (context.row.age > 70) {

// Apply bold styling to the row

// Each cell in the row then inherits that in the usual way context.element.style.fontWeight = "bold";

}

}

});

3.6Grid HTML structure

The Developer Tools in your browser provide the most comprehensive and interactive guide to the HTML and CSS which the grid generates. This section is just an additional reference.

3.6.1The outer wrapper

The grid removes and replaces the contents of the container which you give it, but it does not modify the styles or attributes of your container in any way.

The grid creates its own outer wrapper within your container. This has both an id and a class based on the id which you pass in the grid's configuration (which is auto-generated if null).

Depending on configuration options such as gridlines:false, the grid adds classes such as .fxbgrid-no-filters to its outer wrapper. The grid's standard CSS then turns gridlines etc on and off based on a selector for this additional class.

3.6.2Main areas of the grid

The outer wrapper has four children, with the following class names:

· .fxbgrid-headers

· .fxbgrid-filters

· .fxbgrid-data

· .fxbgrid-footers

Each of these areas is then subdivided into separate fixed-left, central, and fixed-right sections (always created regardless of whether any fixed-left or fixed-right columns exist).

In turn, each of these sub-sections has an outer area, such as .fxbgrid-data-left, and an inner area, such as .fxbgrid-data-left-inner.

3.6.3Rows of data

The sub-division of each grid area means that a row of data is not a single HTML element. A row consists of three separate elements, within the left, central, and right inner areas.

Each row element is styled with .fxbgrid-row and also with either .fxbgrid-row-odd or .fxbgrid-row-even (to handling the optional striping of alternate rows). Each row also has a class based on its zero-based index, such as .fxbgrid-row-index-23. The first row is therefore fxbgrid-row-index-0. The last row in the grid also has .fxbgrid-row-last. Selected rows have .fxbgrid-row-selected.

You can add per-row classes into a row element by defining __cssClass in the metadata for a row (or by using post-rendering).

3.6.4Data cells

Each data cell consists of an outer and inner element. The outer element has .fxbgrid-data-cell and the inner element, which contains the actual rendering of the cell's value, has .fxbgrid-data-cell-inner.

Both the outer and inner elements also have a class name which includes the column's id. For example, for columns: [{id: "firstname"}] :

<div class="fxbgrid-data-cell fxbgrid-data-cell-firstname">

<div class="fxbgrid-data-cell-inner fxbgrid-data-cell-firstname">

You can, for example, uses these per-column classes such as .fxbgrid-data-cell-firstname to style the cells for a column without needing to define additional configuration for that column such as addCssClass:"MyCustomClass".

To assist with displaying images, the cell outer and inner both have the following default styles assigned by the standard CSS:

background-position: center center;

background-repeat: no-repeat;

3.6.5Header cells

Header cells also consist of outer and inner elements, with .fxbgrid-header-cell and .fxbgrid-header-cell-inner.

Both the outer and inner elements also have a class name which includes the column's id. For example, for columns: [{id: "firstname"}] :

<div class="fxbgrid-header-cell fxbgrid-header-cell-firstname">

<div class="fxbgrid-header-cell-inner fxbgrid-header-cell-firstname">

The inner element then has a <span> which contains the text for the column header.

If a column is resizable, then the outer element will contain a resize handle element. If a column is sortable, then the inner element will contain the clickable sort icons.

3.6.6Header divider markers

The grid adds a divider marker into the outer container for each header cell, but these are turned off by default; the standard CSS has visibility:hidden for .fxbgrid-header-divider. You can make the dividers visible (and re-style them) by modifying or overriding the grid's standard CSS.

You can suppress the divider marker for an individual column by setting hideDivider:true, in the column's configuration, or by using CSS such as the following:

/* Hide divider for column with id "firstname" */

.fxbgrid-header-cell-firstname .fxbgrid-header-divider {

visibility:hidden;

}

3.7Vertical centering and block elements

The grid does vertical centering of content within cells using a ::before selector. This has a side-effect: if you add a block element into a cell, e.g. using a cellRenderer, then it will be pushed below the cell bottom and will be invisible. You either need to use display:inline-block, or override the ::before selector for the cell/column.

(We started by using display:flex to do vertical centering. But Safari has some interesting bugs around dynamic creation of flex elements, so we had to switch to using ::before instead.)

4Common grid customization

Most of the topics in this section, plus many others, are illustrated by the example files which are included with the grid.

4.1Displaying a different image in each row

There are several ways of displaying images in cells, depending on considerations such as whether you need the image also to be clickable. Note that a repeated image in each cell, such as an action icon, is usually best handled via CSS as described separately below.

4.1.1Displaying an image by using a cell format specification

The simplest but least flexible way of displaying an image in a cell is to have the image URL in each row of data, and to tell the grid that the column contains an image, using format:{type: "image"} in the column's definition. For example:

var data = [

{rowImage: "myimage.png", … }, …

];

var columns = [

{id: "rowImage", format: {type: "image"}}, …

];

The grid then simply inserts an <img> (without explicit width or height) into the cell, with the src attribute defined by the value from the data row.

A slight variation is to use format:{type: "backgroundImage"}. The grid then displays the image by setting the URL as the background-image style on the cell's inner container.

In either case you cannot detect clicks on the image itself, but you can use the grid's onCellClick to listen for clicks on the containing data cell.

4.1.2Displaying an image by inserting an <img> into a cell

A more flexible way to display an image in each cell is to use a cellRenderer for the column, and to put your own <img> into the cell: For example:

new FXB.ui.Grid({

columns: [

{id: "myimage", cellRenderer: RenderCellImage}

]

});

function RenderCellImage (context)

{

var strImageUrl = … can be a value from context.row, or a derived value, or anything else

var img = document.createElement("IMG");

img.src = strImageUrl;

context.element.appendChild(img);

// Can also handle clicks on the image

img.rowId = context.row[context.grid.rowKey]; // Store value which identifies row

img.addEventListener("onclick", function (e) {

… handle click on image, e.g. using e.currentTarget.rowId

});

}

Instead of listening for clicks on the image, you can also use the grid's onCellClick to listen for clicks on the containing data cell.

4.2Displaying repeated images in each cell

There are two main ways of displaying the same repeated image in each data cell (or a small range of images), such as an "action" icon for each row.

The simplest option is just to put an <img> into each cell, as described above. However, for a repeated identical image in each row, you will generally see better browser performance by using CSS.

For example, you can use the fact that the grid assigns a CSS class to each cell in a column based on the column's id, and use that in combination with the cell's inner container.fxbgrid-data-cell-inner. For example:

columns: [

{id: "actionicon"} // Cells will have the class .fxbgrid-data-cell-actionicon

]

.fxbgrid-data-cell-actionicon .fxbgrid-data-cell-inner {

background-image: …;

background-size: 20px 20px;

}

(Note that the standard CSS automatically gives cells centering of background images, and no-repeat).

Alternatively, you can define a custom cssDataClass to be added into all data cells for the column:

columns: [

{id: "actionicon", cssDataClass: "my-action-icon"}

]

You then define CSS which applies an icon to the combination of .my-action-icon and the cell's inner container.

.fxbgrid-data-cell-inner.my-action-icon {

background-image: …;

background-size: 20px 20px;

}

You can handle clicks on the icon via the onCellClick event:

new FXB.ui.Grid({

onCellClick: HandleCellClick

});

function HandleCellClick(context)

{

if (context.column.id == "actionicon") {

… it's a click on the action-icon cell

}

}

To vary the image for some of the rows, you can assign a __cssClass in the row's metadata, and then use that to change the image. For example:

data = [

{id: 1, value1: 1, value2: 2, value3: 3},

{id: 2, value1: 7, value2: 8, value3: 9, __cssClass: "special-data-row"}

];

.special-data-row .fxbgrid-data-cell-inner.my-action-icon {

background-image: …; /* Different image if row metadata has .special-data-row */

}

Another way of varying the image for different rows would be to use a cellRenderer, or to do post-rendering using onDataCellCreate, while still using pure CSS styling rather than appending an <img>:

function RenderActionIcon(context)

{

// Don't create any content for the data cell, but do add to the classes on

// the cell container depending on the row's contents

if (context.row.somevalue == something) {

context.element.className += " special-action-icon";

} else {

context.element.className += " standard-action-icon";

}

}

4.3Displaying hyperlinks in cells

If your data contains hyperlinks which you want to display, then you can simply use a format specification for the column: format:{type: "link"}. The grid then inserts an <a> into the cell. The format:{} can optionally also contain a target which is added to the <a>. For example:

var data = [

{recordUrl: "https://www.google.com/" , …} …

];

var columns = [

{id: "recordUrl", format: {type: "link", target: "_blank"}}, …

];

You can also use format:{type: "link"} to handle situations where the value to display in a cell and the hyperlink to use are different properties of the data row, using hrefProperty. For example:

var data = [

{siteName: "Google", recordUrl: "https://www.google.com/" , …} …

];

var columns = [

{id: "siteName", format: {type: "link", hrefProperty: "recordUrl", target: "_blank"}}, …

];

The rest of this section deals with scenarios where you want more control over the formatting and behaviour of the link. Let's start by imagining data for the grid which contains a customerId and a homePageUrl, and where clicking on a customer ID in the grid should open the URL.

4.3.1Displaying hyperlinks using a cellRenderer

One simple option is to use a cellRenderer to insert an <a> into the data cells for the column. This is equivalent to format:{type: "link", hrefProperty: "…"}}, but gives you the opportunity for more control over any styling of the <a>. For example:

columns: [{id: "customerId", text: "ID", cellRenderer: RenderID}, …]

function RenderID(context)

{

// Create an A node

var a = document.createElement("A");

a.href = context.row.homePageUrl;

a.innerHTML = context.row.customerId;

context.element.appendChild(a);

}

A more concise version of the same thing using innerHTML would be:

function RenderID(context)

{

context.element.innerHTML = "<a href=\"" + context.row.homePageUrl +"\">" + context.row.customerId + "</a>";

}

4.3.2Displaying hyperlinks by handling onCellClick

Another option is to handle the onCellClick for the grid, and to react to clicks on the specific column. For example:

columns: [{id: "customerId", text: "ID"}, …], // Not using a cellRenderer…

onCellClick: MyClickHandler

function MyClickHandler(context)

{

// Only handle clicks in the column we're interested in

if (context.column.id == "customerId") {

document.location.href = context.row.homePageUrl;

}

}

You can then style the cells as hyperlinks by giving them an additional CSS class. For example:

columns: [{id: "customerId", text: "ID", onCellClick: MyClickHandler, cssDataClass: "link"}, …]

.fxbgrid-data-cell-inner.link {

cursor: pointer;

}

.fxbgrid-data-cell-inner.link:hover {

text-decoration: underline;

}

4.3.3Displaying hyperlinks by handling onDataCellRender

Another way of turning a cell into a hyperlink is to use the grid's onDataCellRender event to do post-rendering modification of the cells for the column. For example, you can change the style of a cell (or add CSS classes to it), and set up a custom click listener:

onDataCellRender: MyPostRenderer

function MyPostRenderer(context)

{

// Only modifies cells in the column we're interested in

if (context.column.id == "customerId") {

context.element.style.cursor = "pointer";

context.element.urlToOpen = context.row.homePageUrl;

context.element.addEventListener("click", function (e) {

document.location.href = e.currentTarget.urlToOpen;

});

}

}

Note: clicks on the cell will then fire both the grid's onCellClick and also the custom listener added above. Therefore, the previous example of handling the hyperlinks via onCellClick is probably preferable to this route on the grounds of efficiency.

4.4Row metadata: heights, selection, CSS classes

The row data which you load into the grid can contain "metadata", such as __height, which controls how the grid formats or processes a row. All the metadata items are denoted by being prefixed with a double underscore.

Metadata

Description

__cssClass

Defines a CSS class (or space-delimited list of classes) to be added into the top-level elements for the row. (See the example above about displaying action icons.)

__height

Defines a height, in pixels, for the row, overriding the standard rowHeight in the grid's configuration. In order for __height to take effect, you must explicitly turn on hasIndividualRowHeights in the grid's configuration, because this can have a (very small) impact on grid performance.

Note: you cannot change the height of a row using a post-rendering function or CSS. You must use the __height metadata. Rows have position:absolute, and therefore changing the height of a row does not adjust the position of rows below it.

__prioritySort

Overrides the sorting on the grid so that rows with a higher __prioritySort are always sorted before rows with a lower value. The __prioritySort is treated as zero if null. Can be used to stick rows to the top or bottom of the grid's sorted order. (For fully "frozen" rows, outside the scrollable area, see the example extension below.) You must specify a rowKey for __prioritySort to take effect.

__selected

Indicates that the row should be shown as selected

__unselectable

Prevents the user selecting a row (or de-selecting it)

__undraggable

Prevents the user dragging a row

In addition to the metadata which you can supply, the grid also maintains metadata of its own which you can inspect when you retrieve row data using a grid method such as GetDataRows(), or when you receive it in an event handler such as footerRenderer.

Metadata

Description

__rowIndex

Zero-based current index of the row within the grid

__rendered

Boolean indicating whether the row has been physically rendered into the grid

__group

Group to which the row belongs (if grouping has been turned on)

__groupData

__groupHeader

__groupSummary

The grid will set one of these to true depending on whether the row is normal data, or the group header, or a group summary row.

__childCount

Only applicable if __groupHeader=true. Number of data rows in the group.

__groupCollapsed

Only applicable if __groupHeader=true. Boolean indicating whether the group is collapsed or expanded.

__isCollapsedGroupRow

Boolean indicating a data row belonging to a collapsed group. Only applicable if __groupData=true (and also only if excludeCollapsedGroupRows is not set).

4.4.1Setting metadata after loading data into the grid

You don't necessarily have to set metadata such as __height before you load data into the grid. You may want to set the height dynamically, depending on other conditions. There are two later points at which you can set/change the metadata.

In onDataInherited. This is called when you create the grid or subsequently Rebind() with new data. Changing the data during onDataInherited is very similar to setting properties before you load it into the grid.

In onDataPrepared. This is called whenever the grid needs to be refreshed (e.g. because of a sorting change). If a row's height needs to change, it is more efficient to do a Refresh() and set the height in onDataPrepared, rather than re-loading data with a full Rebind().

For example, the individual-row-heights-2 file which is included with the grid shows how to make row height dependent on a user-configurable field. It uses onDataPrepared as follows, setting the __height depending on an external threshold.

onDataPrepared: function(context) {

// Get the threshold from a user-configurable <select>

var threshold = …;

// Process the data, setting __height depending on the threshold

for (var i = 0; i < context.data.length; i++) {

var row = context.data[i];

// Set explicit __height, or use default grid.rowHeight

row.__height = (row.age > threshold ? 100 : null);

}

}

4.5.1Right-to-left

The grid has full right-to-left support. It will auto-detect this based on the CSS direction style of its container (but you can also override its handling by passing rtl:true or rtl:false in the configuration). If RTL is turned on, then the grid will default to inverting column alignment: right becomes left, centered remains the same, and left becomes right. You can prevent this by specifying invertColumnAlignment:false in the grid's configuration.

4.5.2Number and date formatting

You can control the formatting of number and date values by providing a cellRenderer function for each column. The grid's philosophy is to not to force you into using a localization framework of its choice rather than your choice.

However, the grid does have some built-in functionality for formatting number and date values. For example, you can set a format specification on a column using format:{type: "x"}:

columns: [

{id: "price", text: "Price", format: {type: "cash"}},

{id: "quantity", text: "Qty", format: {type: "number", digits: 0, thousands_separator: ""}}

]

You can also set default formatting options which are used if an individual column does not have explicit values. For example:

new FXB.ui.Grid({

numberFormatOptions: {digits: 2, decimal_separator: ",", thousands_separator: "."},

cashFormatOptions: {prefix: "£", digits: 2, decimal_separator: ",", thousands_separator: "."},

columns: [

{id: "price", text: "Price", format: {type: "cash"}}, // Will use default (2) digits

{id: "qty", text: "Qty", format: {type: "number", digits: 0}} // Overrides digits

]

});

Numeric and cash formatting can have the following options, either in the individual column definition or in the default numberFormatOptions and cashFormatOptions. The only difference between numeric and cash is that format:{type:"cash"} defaults to prefix:"$" if none is explicitly defined.

Numeric/cash option

Description

digits

Number of decimal digits to display. If null, then max_digits can be used.

max_digits

Only applicable if digits is null. Defines a maximum number of digits to use. For example, with max_digits:2, 107.1 will be rendered as 107.1, and 2.3456 will be rendered as 2.35

decimal_separator

Defines the decimal separator to use. Defaults to .

thousands_separator

Defines the thousands separator to use. Defaults to ,

thousands

Boolean, telling the grid to display numbers as nK or nM if over one thousand or one million. For example, with digits:2 and thousands:true, 11045 will be rendered as 11.05K

show_plus

Boolean, telling the grid to prefix + onto positive numbers. Displayed before any prefix.

prefix

Prefix to add to the rendered value - main purpose is to handle a currency symbol

suffix

Suffix to add to the rendered value. Can be any text. For example, suffix:" widgets" means that the value 2 will be rendered as "2 widgets"

The grid also has basic date formatting options. Dates are always formatted as the neutral yyyy/mm/dd, not regionally, but you can control the separator characters. The different type:X values for different date formats are as follows:

type:X

Description

date

Formats as yyyy/mm/dd hh:mm:ss

dateonly

Formats as yyyy/mm/dd

datehhmm

Formats as yyyy/mm/dd hh:mm

timeonly

Formats as hh:mm:ss

timehhmm

Formats as hh:mm

The additional settings which you can define for date/time formatting are as follows. Like numbers, there can be a dateFormatOptions which defines defaults to use if a column's format:{} specification does not have explicit values. For example:

dateFormatOptions: {date_separator: "-", time_separator: "-", localTime: false}

Date sub-options

Description

date_separator

Separator between date parts, defaulting to /

time_separator

Separator between time parts, defaulting to :

spacer

Spacer between date and time sections, defaulting to a space

localTime

Boolean, defaulting to true. Set to false in order to display the UTC value of the date rather than the local time.

4.5.3Overriding the default formatting of values

As an alternative to using the grid's built-in formatting, a global and blunt way of changing its formatting of values is to completely redefine and override its standard FormatCellValue() method. For example:

FXB.ui.Grid.prototype.FormatCellValue = function(formatObject, value, column, row)

{

// Completely overrides and redefines cell value formatting, for all columns which

// do not have a cellRenderer.

// row is the full row of data.

// column is an object describing the column for which the value is being formatted.

// formatObject is the format:{} specification within that column, which can be null

… return the rendered value to display in the cell, e.g. return value.toFixed(2);

The return value is allowed to be any HTML.

};

While this lets you make a global change to the way values are formatted by the grid, it is fundamentally different to rendering or post-rendering such as cellRenderer or OnDataCellRender. This function returns the formatted value to display in a cell. A renderer or post-renderer gives you access to the HTML container, letting you set styles or event handlers on the container as well as setting/changing its content.

4.5.4Parsing user-entered filter values

Users can enter numeric values as filters for the grid, and you may want to parse these based on the locale - for example, to let continental Europeans use , as a decimal separator. By default the grid will use the standard Javascript parseFloat(). You can override this by supplying a parseUserFloat function in the grid's configuration. For example:

new FXB.ui.Grid({

parseUserFloat: function(text) { … handle regional variants }

});

There is a further consideration related to filtering: users can enter multiple values, and by default they can use either , or ; as a delimiter. Commas can be ambiguous, particularly if the locale uses them as a decimal separator. To remove this ambiguity, you can specify allowCommaFilterSeparators:false in the grid's configuration, allowing only ; to be used as a separator between multiple values in a filter.

4.6Virtual columns and data

Let's say that you have data in the form {firstname: "X", lastname: "Y"} and that you want to display a single name column which combines first name and last name.

In many cases the simplest - and best-performing - answer will just be to pre-process the data before you load it into the grid, adding a name value to each row of data. You could also do this by hooking into the grid's onDataInherited. For example:

onDataInherited: function (context) {

for (var i = 0; i < context.data.length; i++) {

var row = context.data[i];

row.name = row.firstname + " " + row.lastname;

}

}

However, it's also possible to use column rendering and events to handle a "virtual" name field. The complexity of this depends on which grid features you want to use. Let's assume the following:

Name should be displayed as firstname lastname (i.e. last name in bold)

Sorting should be by last name, then by first name

Filtering (on a text-contains basis) should be as displayed, on first name plus last name

(If the column should also be groupable, e.g. by last name, then this adds further complexity not covered here. Please see the virtual-data.html example file for the additional steps required to handle this.)

In order to combine the first name and last name with the features described above, you need to implement cellRenderer, onSortCompare, and onGetFilteringValue for the virtual column:

var columns = [{

id: "name", // Doesn't physically exist in the data

sortable: true,

filter: "textcontains",

cellRenderer: onRenderNameCell,

onSortCompare: onNameSortCompare,

onGetFilteringValue: onNameGetFilteringValue

The first step is to do the rendering of the cell, combining the first name and last name:

function onRenderNameCell(context)

{

var strNameDisplay = context.row.firstname + " <b>" + context.row.lastname + "</b>";

context.element.innerHTML = strNameDisplay;

}

You then need to do a custom sort comparison, looking at last name and then at first name:

function onNameSortCompare(context)

{

// context.valueA and context.valueB will be null, because the data does

// not physically contain a .name value. Need to look at rowA and rowB.

if (context.rowA.lastname.toLowerCase() < context.rowB.lastname.toLowerCase()) {

return -1;

} else if (context.rowA.lastname.toLowerCase() > context.rowB.lastname.toLowerCase()) {

return 1;

} else {

if (context.rowA.firstname.toLowerCase() < context.rowB.firstname.toLowerCase()) {

return -1;

} else if (context.rowA.firstname.toLowerCase() > context.rowB.firstname.toLowerCase()) {

return 1;

} else {

return 0;

}

}

}

Finally, you need to give the grid the value for comparing each row against a filter condition:

function onNameGetFilteringValue(context)

{

return context.row.firstname + " " + context.row.lastname;

}

4.7Row dragging

You can let users drag data rows, i.e. re-order the grid data using drag and drop, by defining a column which acts as a drag handle. (This column will typically be part of any fixed-left group in the grid.)

It's possible to let multiple columns act as drag handles. But defining a column as a drag handle means that, on a touch device, the user cannot scroll the grid on that column, because the gestures for scroll and drag are the same. In other words: if you mark every column as a drag handle, the grid becomes un-scrollable on a touch device.

You define a column as a drag handle by setting rowDrag:true in its definition. You will generally want to display an icon in this column. For example:

columns: [

{id: "draghandle", rowDrag: true, cellRenderer: RenderDragIcon, fixed: true},

{id: "firstname", text: "First name"}

]

When the user re-orders the data using drag and drop, the grid fires its onRowDrag event. You can react to the changes either by examining the moveRow and ontoRow properties of the event context, or by collecting the entire grid data using the GetDataRows() method.

4.8Row and cell selection

The grid allows row, cell, or cell-range selection. There is one mode, cellandrow, which acts as both row selection and cell selection. You control the selection mode by supplying selectionMode in the grid's configuration, e.g. selectionMode:"hybrid".

4.8.1Row selection modes

Mode

Description

single

The user can only select one row at a time. Clicking on a new row de-selects any existing selection. A selected row cannot be de-selected by clicking on it again; once the user clicks on a row there is always then a selection in the grid.

singleonoff

Like single, except that clicking on a selected row does de-select it, leaving no selection.

simple

Multiple rows can be selected. Clicking on a row simply toggles its selection state.

multi

Standard Windows-style multi-select. Clicking on a row selects that row and de-selects any others. Using Ctrl+click toggles the selection of a row (without affecting other selections). Using Shift+click selects all the rows between the previous selection and the new one.

hybrid

Extension of multi, to handle touch devices. In addition to the standard multi-handling, a touch click rather than mouse click acts as a simple on/off toggle (i.e. like Ctrl+click).

When there is any change to selected rows, the grid fires its onRowSelectionChange event. The list of selected rows can be obtained either by listening to onRowSelectionChange, or by using the GetSelectedRows() method.

You can set the selected rows in the grid in two ways:

By using the SetSelectedRows() method

By setting __selected:true in a row's metadata before loading it into the grid

Note: setting the selected rows programmatically is not limited by the selectionMode. You can set multiple rows as selected even if the selectionMode is single.

4.8.2Cell selection modes

Mode

Description

cell

The user can select an individual cell.

cellandrow

The user can select an individual cell, which also selects the row. In addition to cell selection, this behaves like row selection in single mode.

cellrange

The user can select a single cell or rectangular range of cells. A range is reset if any of the following happens: change to sorting, filtering, grouping, or a Rebind() or ChangeColumns() - because the selected range may no longer be contiguous cells.

Note: presence of a rowKey is compulsory in order to use any of the cell selection modes.

When there is any change to the selected cell(s), the grid fires its onCellSelectionChange event. The list of selected cell(s) can be obtained either by listening to this event or by using the GetSelectedCells() method.

You can set the selected cells(s) programmatically using SetSelectedCell() or SetSelectedCells().

Cells can be made un-selectable by giving their outer container element an unselectable attribute in post-rendering. For example:

onDataCellCreate: function(context) {

if (… some condition based on context.column and or context.row … ) {

context.element.setAttribute("unselectable", true);

}

}

4.8.3Copying the row or cell selection to the clipboard

You can get the grid to handle the Ctrl+C shortcut by passing a clipboard value in the configuration. This can be a simple boolean value, or it can be an object specifying row and cell delimiters for the text which is put onto the keyboard. For example:

clipboard: true // Uses the defaults in the next example…

clipboard: {rowDelimiter: "\r\n", cellDelimiter: "\t", headers: false}

When the user presses Ctrl+C, the grid will then look at the current row selection or cell selection, depending on the selectionMode, and copy the formatted textual representation of the cells to the clipboard.

You can specify that the copy should include the text of column headers by including headers:true in the clipboard value.

You can also copy the current selection to the clipboard programmatically, using the CopyToClipboard() method. You can only usually do this from a mouse or keyboard event handler. Browsers generally block access to the clipboard unless initiated by user action.

4.9Saving and loading grid state

The grid provides functions for collecting user-controllable state such as filtering, and re-applying that into the grid in future. You are responsible for how and where the state is stored and reloaded, e.g. using localStorage.

Whenever the user changes some aspect of the grid state, the grid fires its onStateChange event. You can get the state either from the state in the event's context object or, at any time, by calling the GetState() method.

The state reported by the grid covers the following:

Column sorting

Manual changes to column widths

Manual changes to column order (drag and drop)

Any filter values

Any grouping

List of collapsed versus expanded groups

Row and cell selection is treated as transient, and not included in the state. If you want to preserve this, you need to do so yourself. For example:

Listen to the onRowSelectionChange event

Add the list of selected rows into the state reported by the grid

When reloading the grid, set __selected:true in the metadata for each selected row.

If you want to exclude things such as grouping or filtering from the state, then you can simply modify the object which GetState() returns. For example:

var state = grid.GetState();

state.grouping = null;

state.filters = null;

window.localStorage.setItem("gridState", JSON.stringify(state));

You reload stored state by passing an optional second parameter to the FXB.ui.Grid() constructor. For example:

var config = {data: […], columns: […]};

// Read any state which was previously put into localStorage

var state, strStateData;

strStateData = window.localStorage.getItem("gridState");

if (strStateData) state = JSON.parse(strStateData);

// Initialise the grid with both configuration and state

grid = new FXB.ui.Grid({config, state});

In other words: you pass in your standard, pre-defined grid configuration including things such as column order and width. The grid then merges into that any changes as a result of the stored state. You don't need to do modify the grid configuration yourself.

4.9.1Remembering scroll position

The grid's state does not include scroll position. However, the grid does provide help with persisting scroll position.

There are two ways of setting the initial scroll position of the grid when it is created. You can use the initialScrollLeft and initialScrollTop configuration, or you can put scrollLeft and scrollTop properties into a state object which you apply when creating the grid. For example:

var state = {…, scrollLeft: 50, scrollTop: 1000};

var config = {data: […], columns: […]};

new FXB.ui.Grid(config, state);

For example, you could store a grid's scroll position and also the rest of its state using functions such as the following, which handle onScroll and onStateChange.

onScroll: function(context) {

context.grid.myCombinedStateSave(context);

},

onStateChange: function(context) {

context.grid.myCombinedStateSave(context);

},

myCombinedStateSave: function(context) {

// Get grid's state definition

var state = context.grid.GetState();

// Add scroll position into the state definition. The grid will read and use

// these values when the state is reloaded.

Object.assign(state, context.grid.GetScrollPosition());

… store combined state, e.g. in localStorage or sessionStorage

}

When you next create the grid using the saved state, it will then apply the scrollLeft and scrollTop values which are stored in the state.

4.10Overriding the grid's icons

There are four ways of changing the icons which the grid uses:

1. Change the SVG images in the grid's standard CSS, or redefine the same selectors with later definitions, modifying the SVG background-image for classes such as .fxbgrid-icon-sort-asc. If you use this route then the images must be SVG. They cannot be PNG, JPG etc.

2. Delete the icon definitions such as .fxbgrid-icon-sort-asc from the CSS, thus disabling the standard icons. You can then define any background-image of your own on icon classes such as .fxbgrid-header-sorticon-asc and .fxbgrid-row-groupheader-icon-expand. You can also use things such as Font Awesome icons by defining an ::after selector for .fxbgrid-header-sorticon-asc etc.

3. Same as 2, except that you can also disable the standard icons by specifying customIcons:true in the grid's configuration, rather than deleting the icon definitions from the CSS.

4. Handle the grid's events onCreateGroupExpandIcon and onCreateHeaderSortIcon, and create your own HTML for the icon elements.

Partial example of option 2/3 using background images:

.fxbgrid-header-sorticon {

width: 20px; height: 20px; /* Change icon size */

}

.fxbgrid-header-sorticon-asc {

background-image: url("/images/my-sort-asc-icon.png");

}

.fxbgrid-header-sorticon-desc {

background-image: url("/images/my-sort-desc-icon.png");

}

Partial example of option 2/3 using Font Awesome:

.fxbgrid-header-sorticon-asc::after {

font-family: "FontAwesome";

font-size: 14px;

content: "\f0de";

}

.fxbgrid-header-sorticon-desc::after {

font-family: "FontAwesome";

font-size: 14px;

content: "\f0dd";

}

Partial example of option 4:

new FXB.ui.Grid({

onCreateGroupExpandIcon: OnCreateExpandIcon,

})

function OnCreateExpandIcon(context)

{

// Set CSS class on element, depending on whether it's a collapsed or expanded icon

context.element.className = "myicon-" + (context.collapsed ? "collapsed" : "expanded");

// Could just use CSS based on the class assigned above, but we can now do anything

// with the node, such as adding an <img> into it…

var img = document.createElement("IMG");

img.src = "/images/grp-icon" + (context.collapsed ? "collapsed" : "expanded") + ".png";

context.element.appendChild(img);

}

4.11Implementing a mobile-style "Edit" mode

You may want to implement a mobile-style "Edit" mode, where controls for each grid row such as drag handles or deletion icons are only available when the user toggles into an editing view.

For example, you can have your own button (outside the grid) which the user clicks on to toggle editing mode on and off:

<button onclick="ToggleEditMode()">Edit</button>

function ToggleEditMode()

{

// Create/use a custom property in the grid to record edit mode

grid.inEditMode = !grid.inEditMode;

// Force the grid to re-render its columns (without re-evaluating all the data)

grid.CreateStylesheet();

}

One way of controlling the visibility of the edit-mode columns is then simply to use an onGetWidth() function to handle those columns which should only be available in edit mode, hiding them outside edit mode by setting their width to zero.

columns: [

{id: "handle", rowDrag: true, fixed: true, onGetWidth: OnlyInEditMode},

{id: "columntext", fill: true},

{id: "deleteicon", onGetWidth: OnlyInEditMode}

];

function OnlyInEditMode(context)

{

return context.grid.inEditMode ? 30 : 0; // 30 pixels if in edit mode, otherwise hidden

}

4.12Disabling filtering while the filter bar is hidden

You can show and hide the grid's filter bar using the ShowFilterBar() method. By default, filtering continues to be applied while the bar is hidden. You can change this using the SetIgnoreFilters() method. For example, the following button toggles the state of the filter bar and disables filtering while the bar is hidden:

<button onclick="ToggleFilterBar()">Toggle</button>

function ToggleFilterBar()

{

grid.ShowFilterBar(grid.hideFilters); // Note: this changes the value of hideFilters

grid.SetIgnoreFilters(grid.hideFilters);

}

4.13Responsive column options

Columns can have widths specified as a percentage, limited also by a minWidth and maxWidth, and can therefore automatically grow and shrink when the grid changes size.

However, it is also possible to do more comprehensive responsive handling of different container sizes by having different column definitions which depend on the container width. You specify multiple sets of columns for different container widths by initializing the grid with a columnOptions[] array instead of a columns[] array. For example:

new FXB.ui.Grid({

columnOptions: [

{id: "max500", maxContainer: 500, columns: [ … ] },

{id: "max800", maxContainer: 800, columns: [ … ] },

{id: "standard", columns: [ … ] }

],

});

Each entry in the columnOptions array consists of the following:

An ID. This is not compulsory, but the grid cannot remember the state of column widths and dragged positions unless you specify a unique ID for each option.

The maximum container width to which the option applies. In the first example above, that set of columns will be used if the container width is no more than 500 pixels. Any maxContainer is ignored and not required on the last entry in the options list.

An array of columns to use at the specified resolution (like a normal columns[] configuration).

A fairly realistic example of column options looks like this:

[

{id: 400, maxContainer: 400, columns: [

{id: "firstname", width: "50%"},

{id: "lastname", width: "50%"}

]},

{id: 700, maxContainer: 700, columns: [

{id: "firstname", width: "40%"},

{id: "lastname", width: "40%"},

{id: "age", width: "20%"}

]},

{id: "standard", columns: [

{id: "firstname", width: "30%", maxWidth: 300},

{id: "lastname", width: "30%", maxWidth: 300},

{id: "age", width: "20%", maxWidth: 150},

{id: "gender", width: "20%", maxWidth: 200},

{id: "dummyspacer", spacer: true}

]}

]

The effect of these options would be as follows:

With a container of up to 400px, first and last name are displayed.

With a container of 401-700px, an additional column, for age, is displayed.

With any container wider than 700px, a fourth column, for gender, is displayed. In addition, the columns each get a maxWidth preventing them becoming hugely spaced out on a very large screen, and there is a spacer column to fill any unused space in the grid.

4.14Adding custom filtering options

You can implement your own filtering on columns by doing three things:

Set the column's filter property to custom

Implement filterRenderer for the column, to handle display and input

Implement onFilterCompare, to handle the actual data filtering

The following is a simple example of a textual ends-with filter, where the word "Filter" is displayed in the column's filtering cell, and clicking on that text pulls up a prompt to change the filter value:

columns: [

{id: "mytext", filter: "custom", filterRenderer: myFilterRenderer, onFilterCompare: myFilterCompare}

]

function myFilterRenderer(context)

{

var aFilter = document.createElement("A");

aFilter.innerHTML = "Filter";

aFilter.href = "#";

// Give the element some references to the column and grid so that they're

// easily accessible in a click via e.currentTarget

aFilter.column = context.column;

aFilter.grid = context.grid;

aFilter.addEventListener("click", function(e) {

var res = window.prompt("Filter:", e.currentTarget.column.filterCondition || "");

// Store the new filter value against the column

e.currentTarget.column.filterCondition = res;

// Tell the grid to reapply all filtering

e.currentTarget.grid.ReApplyFilters();

// Prevent hyperlink from firing

return false;

});

context.element.appendChild(aFilter);

}

function myFilterCompare(context)

{

// context.row contains the row data

// context.condition contains the content of the user's input into the prompt()

// Exit immediately if the search text is blank

if (context.condition == "") return true;

// Get the cell value from the row and force it to be textual

var cellValue = context.row[context.column.id] + "";

// Does the cell value end with our input? (Note: Internet Explorer doesn't have endsWith)

return cellValue.endsWith(context.condition);

}

Notes on the above example:

You must store the current value of your filter into the filterCondition of the grid column. The grid will not call your onFilterCompare if the columns's filterCondition is null. (But the format in which you store the data is entirely up to you.)

When you change the stored value of your filter, you need to call the grid's ReApplyFilters() method to make it re-evaluate all the data.

4.15Using data without cloning

As explained in the introduction, the grid clones any data which you pass to it, thus stripping out any functions and non-enumerable properties. You can change this behaviour by setting noClone:true in the grid's configuration.

Note: on large datasets such as a million records, setting noClone:true can substantially speed up the grid.

Setting noClone:true means that the grid can then change your source data - for example, it will write its metadata into your source data - but does allow some powerful effects.

For example, consider the following object where the __cssClass in the metadata is a derived property of the object's quantity, giving the row a class which highlights it if the row has a quantity which exceeds a value defined held in document.HighlightThreshold.

function ExampleRowClass(init)

{

for (var k in init) this[k] = init[k];

}

Object.defineProperty(ExampleRowClass.prototype, "__cssClass", {

get: function() {

return document.HighlightThreshold >= 100 ? "HighlightRow" : null;

}

});

The CSS class for highlighting would be defined as something such as the following:

.HighlightRow .fxbgrid-data-cell {background: orange;}

Example columns and data as follows, setting the initial HighlightThreshold to 100:

document.HighlightThreshold = 100;

// Example "Item" and "Quantity" columns

var columns = [{id: "item", text: "Item"}, {id: "quantity", text: "Qty", align: "right"}];

// Build some example data with quantity ranging between 0 and 199

var data = [];

for (var i = 0; i < 100; i++) {

data.push(new ExampleRowClass({rowId: i, item: "Item " + i, quantity: Math.floor(Math.random() * 200)}));

}

grid = new FXB.ui.Grid({

noClone: true,

columns: columns,

data: data,

});

If you pass this data into the grid with cloning enabled, the __cssClass will be discarded (or, if the property is marked as enumerable, then it will be converted from a derived function into a static value).

If cloning is turned off, and you are thus passing the full object into the grid, then some powerful effects become available very cheaply. For example, the following two lines of code would change the highlighting in the grid, picking out values >= 50 rather than >= 100, without needing to modify any of the data rows or to Rebind() new data into the grid:

document.HighlightThreshold = 50;

grid.Refresh();

To spell out the difference caused here by noClone:true:

The Refresh() causes the grid to rebuild the HTML for its data

The rebuild re-evaluates the __cssClass for each row

Turning off cloning means that the __clsClass is a dynamic property rather than a static value.

Thus the Refresh() causes a re-evaluation of each row in relation to the current value of document.HighlightThreshold

4.16Parent and child rows

There is more than one way of implementing "parent and child" rows - i.e. where a parent row has expand and collapse icons for showing and hiding associated child rows.

This will almost always involve an icon column, and using onCellClick to take action when the expand/collapse icon is clicked. It may also be necessary to hook into onDataPrepared, in order to do a post-sorting revision to ensure that child rows are always sorted next to their parent row (as illustrated by the example files included with the grid).

The simple way of handling the expand/collapse is simply to Rebind() the grid with new data which does or does not include child rows for the parent which has been clicked on.

An alternative, which can be slightly more efficient because it does not involve a full Rebind(), is to turn off cloning and to use a dynamic __height property in the metadata for each row which shows or hides child rows.

Step 1 is to create a class to wrap each row of data:

function ExampleRowClass(init)

{

for (var k in init) this[k] = init[k];

}

Let's assume that each row in the data has a personId property; children have a non-null parentId; and that parents have hasChildren:true.

The next step is to give the data class a dynamic __height property. Parent rows always have null height, meaning that the grid's default rowHeight is used. Child rows have either null or 0, depending on whether their parent is expanded or collapsed. (We set up the expandedParents later.)

Object.defineProperty(ExampleRowClass.prototype, "__height", {

get: function() {

if (!document.MyGrid) return null; // Grid doesn't exist yet

if (this.parentId == null) {

// Not a child. Make visible by returning null, i.e. default rowHeight

return null;

} else {

if (document.MyGrid.expandedParents[this.parentId]) {

// Parent is expanded. Make visible by returning null

return null;

} else {

// Parent is collapsed. Hide by returning zero

return 0;

}

}

}

});

Example data and column construction then looks like this, with each row of the data being wrapped by the ExampleRowClass and its dynamic __height property:

var data = [

new ExampleRowClass({personId: 1, name: "Jane Parent", hasChildren: true}),

new ExampleRowClass({personId: 2, name: "Child 1", parentId: 1}),

new ExampleRowClass({personId: 3, name: "Child 2", parentId: 1}),

new ExampleRowClass({personId: 10, name: "John Not-Parent", hasChildren: false})

];

var columns = [

{id: "expandcollapseicon", cellRenderer: RenderExpandCollapseIcon},

{id: "name", text: "Name"}

];

The grid needs to be created with noClone:true, because we want it to preserve the dynamic __height property inside the data, and the grid also needs hasIndividualRowHeights.

document.MyGrid = new FXB.ui.Grid({

noClone: true,

hasIndividualRowHeights: true,

columns: columns,

data: data,

});

We store in the grid a custom object which acts as a simple hash table of which parents are expanded. If document.MyGrid.expandedParents[<parentId>] is non-null, then the __height dynamic property will return null rather than 0, making those child rows visible.

document.MyGrid.expandedParents = {};

Finally, we handle onCellClick in the grid, creating or removing entries in document.MyGrid.expandedParents when a parent is clicked on, and doing a Refresh() of the grid.

document.MyGrid.onCellClick = function(context) {

if (context.column.id == "expandcollapseicon") {

if (context.row.hasChildren) {

// Toggle expansion by creating or deleting an entry in the hash table

if (context.grid.expandedParents[context.row.personId]) {

delete context.grid.expandedParents[context.row.personId];

} else {

context.grid.expandedParents[context.row.personId] = true;

}

// Refresh the grid, causing it to re-evaluate the __height for each row

context.grid.Refresh();

} else {

// (Not a parent row)

}

}

};

All this is potentially more complicated than doing a simple Rebind() with the addition or removal of child rows, but can be a little more efficient because it only involves re-evaluating the data rather than completely reloading it.

4.17Using different row heights on mobile

It's common for a page to use a larger font size when viewed on mobile devices. If you do this, you will probably also want to use a larger rowHeight etc for the grid, to accommodate the larger font size.

The grid provides two ways of varying values such as rowHeight automatically, without having to use code such as the following:

new FXB.ui.Grid({

rowHeight: (onMobileDevice ? 50 : 30)

}

4.17.1Defining responsive heights using CSS

If you don't provide configuration values for settings such as rowHeight, the grid will calculate a default from the following CSS classes:

CSS class

Description

.fxbgrid-default-rowHeight

Controls the default rowHeight

.fxbgrid-default-headerHeight

Controls the default headerHeight

.fxbgrid-default-filterHeight

Controls the default filterHeight

.fxbgrid-default-footerHeight

Controls the default footerHeight

.fxbgrid-default-groupHeaderHeight

Controls the default groupHeaderHeight

.fxbgrid-default-columnWidth

Controls the default for defaultColumnWidth

Therefore, the following CSS will cause the height of rows to change if the horizontal width of the browser window is less than 600 pixels:

.fxbgrid-default-rowHeight {height: 25px;}

@media screen and (max-width: 600px) {

.fxbgrid-default-rowHeight {height: 50px;}

}

Similarly, this means that you can use a different row height (or header height etc) for different grids via # selectors, as well as by providing different values for rowHeight in the Javascript configuration of the grids. For example:

#grid1 .fxbgrid-default-rowHeight {height: 30px;}

#grid2 .fxbgrid-default-rowHeight {height: 40px;}

4.17.2Defining responsive heights using Javascript

As well as using CSS, you can provide a value for the grid's rowHeight configuration as a definition such as the following:

rowHeight: {multiplier: 2.5}

This means "2.5 times the font size in use on/inherited by the grid's container element". For example, if the font size on the grid container is 14px, the grid will use a row height of 35 pixels. As a result, if your page uses different font sizes on mobile and desktop, the grid's row height will then change automatically.

The font size which is applied or inherited on the container element must be specified in pixels in order to use a multiplier. It cannot be specified as em, %, pt etc.

You can use a multiplier definition with the following grid properties. Multipliers for column widths will obviously typically be much larger than multipliers for row heights.

rowHeight

groupHeaderHeight

headerHeight

filterHeight

footerHeight

__height metadata for rows

defaultColumnWidth

Individual width, minWidth, and maxWidth definitions for columns

Note: the trigger for the grid changing its row height etc is a resize of the grid. If you simply change the font size applied on the container element without causing a resize of the grid, then the row heights etc will not update until/unless you call Refresh().

4.18Displaying a busy/waiting notification while the grid updates

Initial construction of the grid and subsequent updates should generally be very fast, unless you are using something like an inordinately complex cellRenderer. Nevertheless, if you want to display some kind of busy/waiting image while the grid updates, the easiest way to do so is by hooking in to the onDataChange event. For example:

grid.onDataChange = function(context) {

if (context.start) {

// An update is starting. Display a CSS loader or similar.

} else {

// Update is complete (and context.finish will be true). Hide image.

}

};

5Grid reference

The following section describes the configuration options, properties, events, and methods of the grid itself. Columns are sufficiently extensive that they are covered in a separate section below.

5.1Grid configuration parameters

The following properties can be passed in the grid's initial configuration. This list excludes the columns[] definition, or the columnOptions[] responsive specification. Configuration values for columns are described below in the column reference.

Property

Description

allColumnsDraggable

Boolean, defaulting to false. Makes each column draggable unless it has an explicit draggable:false. Equivalent to defaultColumnDef:{draggable:true}. Not applied to spacer columns.

allColumnsResizable

Boolean, defaulting to false. Makes each column resizable unless it has an explicit resizable:false. Equivalent to defaultColumnDef:{resizable:true}. Not applied to spacer columns.

allColumnsSortable

Boolean, defaulting to false. Treats each column as sortable:true if it does not have its own explicit definition. Equivalent to defaultColumnDef:{sortable:true}. Not applied to spacer columns.

allowCommaFilterSeparators

Boolean, defaulting to true. By default, filter conditions typed in by the user can have either , or ; as a separator between multiple values. In some regions a comma is used as the decimal marker in numbers, and then becomes ambiguous. To handle these regions, you can turn off allowCommaFilterSeparators so that only ; can be used as a separator, and , is therefore interpreted as part of a number. See also parseUserFloat.

allowFocus

Determines whether the grid can receive input focus, and can fire its onKeyDown and onKeyUp events. Defaults to false unless keyboard processing is implicitly required by turning on clipboard or keyboardNavigation. If the grid is receiving focus, you can control its tab index using the tabIndex property.

alwaysProcessAllFilters

Boolean, defaulting to false. The grid has a CSS class which it sets on filter fields which are having an effect in terms of removing rows. The default CSS does not highlight fields which have a value but are not changing the data ("active" rather than "effective"). If there are multiple filter conditions then, by default, the grid stops evaluating as soon as a row is excluded. If a row would be removed by more than one filter, then the second one may not be marked as effective because it is not evaluated. It would become effective if the first filter condition were removed. If you want to ensure that the highlighting of effective conditions is always fully indicative, then you can turn on alwaysProcessAllFilters. However, this creates a performance penalty, evaluating unnecessary filter conditions, in order to change the meaning of highlighting in the UI from "is currently having an effect" to "would have an effect in isolation".

border

Boolean, defaulting to true. If turned off, the grid's outer wrapper has no border (by setting .fxbgrid-no-border on the wrapper)

cashFormatOptions

See notes on formatting and localization

clipboard

Any non-null value turns on Ctrl+C handling for copying the current selection to the clipboard.

collapseGroupsByDefault

Boolean, defaulting to false. If turned on, all groups are collapsed by default rather than expanded when the grouping is changed

cssClass

Optional additional CSS class to be added to the grid's outer wrapper.

customIcons

Boolean, defaulting to false. See the information on overriding the grid's icons.

dateFormatOptions

See notes on formatting and localization

defaultColumnDef

Defines defaults which are applied to a column if it does not have its own explicit values (except for spacer columns, which are ignored). For example, marking all columns as draggable, and with a text-start filter:

defaultColumnDef: {

draggable: true,

filter: "textstart"

}

defaultColumnWidth

Default width for columns, in pixels, if one does not have an explicit width and no defaultColumnDef.width is specified. Defaults to the CSS definition of .fxbgrid-default-columnWidth if not specified.

excludeCollapsedGroupRows

Boolean, defaulting to false. Changes the behaviour of GetDataRows(), of GetData(), and of the includedDataRowCount property. If turned on, these do not include rows for collapsed groups.

filterHeight

Height in pixels for the grid's filtering row. the CSS definition of .fxbgrid-default-filterHeight if not specified. Can be defined as multiplier of the base font size, such as filterHeight: {multiplier: 2}

fireContainerEvents

Tells the grid to fire all its events as browser events on the container object, as well as calling handler functions such as onCellClick or onDataPrepared. Can be either a simple boolean, fireContainerEvents:true, an array of the events to be fired such as fireContainerEvents:["cellClick", "rowClick"]. See the notes above on the possible performance implications.

footerHeight

Height in pixels for the grid's footer row (if turned on). Defaults to the CSS definition of .fxbgrid-default-footerHeight if not specified. Can be defined as multiplier of the base font size, such as footerHeight: {multiplier: 2}

fullRefreshOnResize

Tells the grid to do a full refresh and re-render whenever its size changes. Can be very expensive, particularly if the size is being incrementally changed by dragging. Should only be necessary in very exceptional cases where you are e.g. deriving dimensions from the overall window size/properties.

gridlines

Boolean, defaulting to true. If false, then the grid adds the .fxbgrid-no-gridlines class to its outer element, turning off gridlines.

grouping

Initial grouping of the grid. Either null or the name of the row property to group by. Can be changed after creating the grid using SetGrouping().

groupHeaderHeight

Height in pixels for each group-header row in the grid. Defaults to the CSS definition of .fxbgrid-default-groupHeaderHeight if not specified. Can be defined as multiplier of the base font size, such as groupHeaderHeight: {multiplier: 2}

groupSummaryAtTop

Boolean, defaulting to false. Positions group summary rows at the top of the group instead of the bottom.

groupToggleFromWholeRow

Boolean, defaulting to true. Unless turned off, the whole of a group header row, rather than just its icon, acts as a toggle for switching the group between expanded and collapsed.

hasIndividualRowHeights

Boolean, defaulting to false. Must be turned on in order to use different heights for each row (via a __height property in the metadata for a row). Can have a (very) small performance penalty, hence the need to turn the property on explicitly.

headerHeight

Height in pixels for column headers. Defaults to the CSS definition of .fxbgrid-default-headerHeight if not specified. Can be defined as multiplier of the base font size, such as headerHeight: {multiplier: 2}

headerLongTapMS

Timeout in milliseconds for the grid's special gesture where a long hold/tap over a header groups by that column. Defaults to 1000 (1 second). A value of zero disables the gesture.

hideFilters

Hides the filter row in the grid. Can be subsequently changed using the ShowFilterBar() method.

hideFooters

Hides the footer row in the grid. Can be subsequently changed using the ShowFooters() method.

hideHeaders

Hides the column headers. Can be subsequently changed using the ShowHeaders() method.

id

Optional ID for the grid's outer HTML wrapper. If no ID is supplied then one is auto-generated. The value is also added as a class, meaning that the CSS for the outer grid element looks like this:

<div id="value" class="value"></div>

ignoreFilters

Boolean, defaulting to false. Tells the grid not to apply any filters which the user has defined. Intended for use with paging.

ignoreGrouping

Boolean, defaulting to false. Tells the grid not to apply any grouping which the user has defined. Mostly intended for use with frozen rows.

initialScrollLeft

Sets initial horizontal scrolling for the grid when it first loads, in pixels.

initialScrollTop

Sets initial vertical scrolling for the grid when it first loads, in pixels.

invertColumnAlignment

Inverts the specified alignment of all columns, so that right becomes left; centered remains the same; and left or unspecified becomes right. Intended for use with right-to-left languages. If omitted, then it defaults to the same value as rtl

keyboardNavigation

Turns on the ability to navigate the grid using arrow keys.

multiSort

Boolean, defaulting to false. Turns on the ability to sort by multiple columns. With keyboard & mouse, the UI for selecting multiple columns is to hold down the Shift or Ctrl keys while clicking on the second and subsequent columns. On a touch device, the UI is a two-fingered tap on a column header.

noClone

By default, the grid takes a full deep clone of the data array which you pass to it, and thus strips out any functions and non-enumerable properties. You can change this by specifying noClone:true, in which case the grid does a slice(0) on the array which you give it. This means that the grid is using references to your original data, not a clone of that data - and will therefore write its metadata into your original row objects. See the example above for potential uses. On large datasets, e.g. 1 million records, setting noClone:true can substantially speed up the grid.

noHeaderGroupIcons

Boolean, defaulting to false. If true, turns off the bar in column headers which users can click on to group by that column. (You then need to provide your own functionality outside the grid for changing the grouping, calling the SetGrouping() method in order to implement the change.)

numberFormatOptions

See notes on formatting and localization

parseUserFloat

Function used for parsing numbers entered by the user into filter fields. Defaults to the standard Javascript parseFloat() if not specified. Allows you to localize the format of number input. See also allowCommaFilterSeparators.

refreshDelayMS

Asynchronous delay, in milliseconds, when calling Refresh() or Rebind(), so that any UI change which you make in response to onDataChange has an opportunity to be rendered by the browser before the potentially long-running operation starts. This means that, by default, Refresh() or Rebind() are asynchronous. To make them fully synchronous, set refreshDelayMS to zero.

resetScrollOnSort

Boolean, defaulting to false. If true, the grid scrolls back to the top whenever the sorting is changed.

resizeDelayMS

Delay, in milliseconds, before processing a resize of the grid container. Defaults to zero. You may want to use this setting in scenarios where the size of the grid may often be incrementally changed as a result of dragging some parent container.

restrictColumnResize

Boolean, defaulting to true. Unless turned off, when the user resizes a column, it is restricted by any minWidth and maxWidth.

rowHeight

Height, in pixels, for each data row in the grid. Defaults to the CSS definition of .fxbgrid-default-rowHeight if not specified. Can be defined as multiplier of the base font size, such as rowHeight: {multiplier: 2}

rowKey

Defines the property in each data row which uniquely identifies the row. Note: on large datasets, such as 1 million records, omitting the rowKey can slightly improve grid performance - provided that you don't need functionality which requires a rowKey, such as UpdateCell() or UpdateRow().

rtl

Boolean value which turns on right-to-left handling. If omitted, the value is auto-detected based on whether the grid's container has (or inherits) the CSS style direction:rtl

selectionMode

Row or cell selection mode. If the value is omitted or "none", then selection is turned off. Can be changed after grid creation using SetSelectionMode().

sort

Initial sorting of the grid. Can be either the string ID of the field to sort by; an object defining both field and direction: {field: "id", direction: "desc"}; or an array of strings or objects for multi-column sort. After initialization, the sorting is then obtainable using the currentSort property.

stripeRows

Boolean, defaulting to false. If true, the grid adds .fxbgrid-stripe-rows to the grid's outer wrapper, turning on different colours for even-numbered rows in the grid.

tabIndex

Controls the HTML tabIndex attribute which is set on the grid's container if is receiving focus for keyboard events (because you have explicitly set allowFocus, or implicitly because you have requested clipboard or keyboardNavigation). Defaults to zero.

virtual

Boolean, defaulting to true. The grid's standard behaviour is that it only creates HTML for visible rows (i.e. visible within the grid's scrolling viewport). By setting virtual:false you can force the grid always to create the HTML for all grid rows. Unsurprisingly, this can damage performance with large datasets.

virtualNoDestroy

Boolean, defaulting to false. Turning this on stops the grid from destroying elements when they are scrolled out of view. This may slightly improve scrolling speed, particularly on legacy browsers such as Internet Explorer. But it can also adversely affect the rendering speed of effects such as highlighting a range of selected cells.

5.2Grid properties

Once created, the grid has some further properties in addition to the initial configuration described above.

Some grid properties have wrapper methods such as ShowFilterBar(). Changing a property such as gridlines or stripeRows after creation does not immediately update the grid; you must also call either CreateStylesheet() or Refresh(). As a general rule, anything which involves a change to the display of the data area, such as altering rowHeight, requires a full Refresh(). Changes limited to the structure or cosmetics, such as altering gridlines or stripeRows, only requires a more lightweight call to CreateStylesheet().

Property

Description

columns

Returns the array of columns currently in use in the grid. Also available via the GetColumns() method. To change the columns, load in a new array using the ChangeColumns() method.

currentSort

Current sorting of the grid. Null if none. Otherwise, a single object defining the field and direction in the form {field: "id", direction: "asc|desc"}, or an array of objects if multi-column sort is turned on and multiple sort conditions are defined. Can be changed using SetSort().

data

Returns the current array of grid data after any sorting, grouping, and filtering has been applied. Equivalent to calling GetData().

dataRowCount

Number of rows in the grid data after applying filtering, but before applying grouping. See also includedDataRowCount.

hasActiveFilters

Boolean indicating whether any column has a filter value defined for it

includedDataRowCount

Number of rows in the grid after taking into account both filtering and grouping, but not counting group header rows. (Potentially affected by excludeCollapsedGroupRows in the grid configuration.)

isDirty

Boolean indicating whether an UpdateCell() or UpdateRow() has left the grid in a dirty state, and a Refresh() is required to make the sorting and filtering consistent with the new values.

isFiltered

Treatable as boolean, indicating whether the grid currently has any rows removed by filtering, but in fact returns the number of rows which have been excluded by filters.

rawDataRowCount

Number of rows in the raw source data for the grid, before applying any filtering or grouping

As an aid to customizing the grid, there are also references to the parts of the grid's HTML structure:

Property

Description

htmlOuter

Reference to the grid's outer wrapper

htmlHeaders

Reference to the container for the headers

htmlHeadersLeft

Container for fixed-left headers

htmlHeadersCentral

Outer container for scrollable, central headers

htmlHeadersCentralInner

Inner container for scrollable, central headers

htmlHeadersRight

Container for fixed-right headers

htmlFilters

Reference to the container for the filter row

htmlFiltersLeft

Container for fixed-left filter cells

htmlFiltersCentral

Outer container for scrollable, central filter cells

htmlFiltersCentralInner

Inner container for scrollable, central filter cells

htmlFiltersRight

Container for fixed-right filter cells

htmlData

Reference to the container for the data area

htmlDataLeft

Container for fixed-left data cells

htmlDataCentral

Outer container for scrollable, central data cells

htmlDataCentralInner

Inner container for scrollable, central data cells

htmlDataRight

Container for fixed-right data cells

htmlFooters

Reference to the container for the footer row

htmlFootersLeft

Container for fixed-left footer cells

htmlFootersCentral

Outer container for scrollable, central footer cells

htmlFootersCentralInner

Inner container for scrollable, central footer cells

htmlFootersRight

Container for fixed-right footer cells

5.3Grid renderer functions

Most rendering events are fired on an individual column. However, there are some global rendering functions which you can use to override the HTML of the grid if no specific renderer is defined for a column. These global renderers receive the same context object as a column renderer.

Renderer

Description

globalCellRenderer

Global cell-rendering function. If a column does not have its own cellRenderer, then the grid will call any globalCellRenderer which you define.

globalHeaderRendererOuter

Global renderer for completely overriding the contents of every column's header cell. Used if a column does not have its own headerRendererOuter.

noDataRenderer

Special renderer which is called if the grid has no data

5.4Grid events

You can define handlers for the following grid events. Your handler function receives a single parameter: a context object. This object always contains a grid property which is a reference to the grid object. Other common properties are row and column, providing any applicable column object and row of data. Some events have further parameters in their context, described below.

If the trigger is a browser event such as click, then the context object will contain an event property which is the Javascript event giving rise to the notification.

Some events can be used to prevent standard grid functionality, such as column dragging or sorting. If you provide a handler for such an event then you must explicitly return true from your function in order to allow the standard grid behaviour to continue.

Some actions in the grid cause multiple events to fire. For example, if the user clicks on a column header to change the sorting, then that will fire onHeaderClick, onSortChange, and onStateChange.

Event

Browser event?

Description

onCalculateGroupSummary

N

Lets you calculate summary rows for each group. The context contains a groupRows[] array listing the data belonging to the group, and a group value identifying the group. You return a row object containing whatever totals, averages etc you want for each column in the grid. If your function returns null, no summary row is added to the group. Your handler will be called for groups regardless of whether they are expanded or collapsed, and the state of the group will be specified by the context's groupCollapsed boolean. You can display summary rows for collapsed groups. If you don't want to do this, you must explicitly check the groupCollapsed value and return null.

onCellClick

Y

Fired when the user clicks on a cell in the grid

onCellContextMenu

Y

Fired when the user right-clicks on a cell in the grid

onCellSelectionChange

N

Fired when the cell selection changes (only applicable if using a cell selectionMode). The new selection may be either a single cell or a range. The context object always contains rows[] and columns[] arrays which, together, identify the selected cell(s). There is also a range boolean. If this is false, then the selection is a single cell, and the context object will also contain row and column properties (which are identical to row[0] and column[0]).

onCellSwipe

Y

Helper for detecting swipe gestures on a cell. In addition to row and column properties identifying the cell, the context object also contains a swipe value which will be one of "left", "right", "up", or "down". Your function must return true if it wants to prevent standard browser handling of the event such as scrolling. Although mainly intended for touch devices, onCellSwipe will also be fired for mouse-swipes.

onColumnDrag

N

Called on completion of a valid column drag. Your function must return true to allow the drag to proceed. The context object contains moveColumn and ontoColumn properties.

onColumnResize

N

Called on completion of a column resize. Your function must return true to allow the resize to be processed. The context object contains a newWidth property defining the revised width in pixels.

onCreateGroupExpandIcon

N

Called when the grid is creating an expand or collapse icon for a group row, allowing you to set the HTML for it. The context object contains element which is the HTML node to be added, and a collapsed boolean value defining whether the icon is for a collapsed or expanded row. See the information on overriding the grid's icons.

onCreateHeaderSortIcon

N

Called when the grid is creating a sorting icon for a column header, allowing you to set the HTML for it. The context object contains element which is the HTML node to be added, and a direction defining whether the icon is for an ascending ("asc") or descending ("desc") icon. See the information on overriding the grid's icons.

onDataCellCreate

N

"Post-rendering function" fired after the construction of the HTML for an individual data cell has been completed. The context contains element which is the outer container for the cell. Vary similar to onDataCellRender, but fires at a slightly later point in the grid construction, and the element is the outer rather than inner cell container.

onDataCellRender

N

"Post-rendering function" fired after any data cell has been rendered, allowing you to attach event handlers to its HTML). The context contains element which is the inner container for the data cell. For example, see the section above about adding hyperlinks into a cell.

onDataChange

N

Fired at the start and end of reprocessing of the grid's data, mainly intended to give you an opportunity to show and then hide a please-wait/loading image. The context contains either start:true or finish:true. See also refreshDelayMS.

onDataInherited

N

Called when data is loaded into the grid, on creation or on Rebind(), before any filtering, grouping, and sorting are applied. Can be used as an opportunity to insert extra properties into your data, as described in the virtual data example, or to add/change metadata such as __height. The context object contains a data property containing the array of rows.

onDataPrepared

N

Called when the grid has finished applying any filtering, grouping, and sorting to the data, and is about to render it. The context contains a data property defining the included rows. It is permissible at this point to do things such as changing the __height property in each row's metadata (which is more efficient than loading whole new data into the grid with a Rebind). Can be used e.g. to modify the data to retain/reimpose parent/child relationships after sorting.

onFilterCellCreate

N

"Post-rendering" function fired after the grid has created the HTML for a column filter cell, e.g. allowing you to attach event handlers to it. The context contains an element which is the outer container for the filter.

onFilterChange

Y

Called when the user is changing the filtering on a column, by pressing Enter or tabbing away from the filter field. Your function must return true to allow the new filter to be applied. The new filter value is in the context's newValue. The event.currentTarget of the context refers to the filter input or select field.

onFooterCellCreate

N

"Post-rendering" function fired after the grid has created the HTML for a column footer, e.g. allowing you to attach event handlers to it. The context contains an element which is the outer container for the footer.

onFooterClick

Y

Click on a cell in the footer row

onFooterContextMenu

Y

Right-click on a cell in the footer row

onGridRender

N

Called on completion of new rendering of grid data. The context object contains either initial:true if the grid data is being rebuilt, e.g. on start-up or after a change of filtering, or virtual:true if the rendering is an addition of new visible rows following a scroll.

onGroupingChange

N

Called when the user is changing the grouping on the grid. Your function must return true to allow the new grouping to be applied. The selected column is specified by the context's newGrouping, and enable specifies whether grouping will be turned on or off (because clicks on the grouping icon are a toggle).

onGroupExpandCollapse

N

Called when a group is expanded or collapsed, either by the user clicking on its header or programmatically using ExpandGroup() or CollapseGroup(). The context object contains the group identifier, plus boolean expand and collapse properties of which one will be true and the other false.

onGroupRowClick

Y

Called when the user clicks anywhere on a group header row. If groupToggleFromWholeRow is turned on, then your function must return true to allow the expand/collapse to proceed.

onHeaderCellCreate

N

"Post-rendering" function fired after the grid has created the HTML for a column header, e.g. allowing you to attach event handlers to it, or modify the standard HTML. The context contains an element which is the outer container for the header.

onHeaderClick

Y

Called when the user clicks on a column header. If the column is sortable, your function must return true to allow the sort to proceed.

onHeaderContextMenu

Y

Right-click on a column header

onKeyDown

Y

Key-down event anywhere within the grid. Not fired unless the grid is configured to get input focus, either explicitly via allowFocus, or implicitly because you turn on clipboard or keyboardNavigation. You must return true from your handler to allow the grid's keyboard handling such as Ctrl+C or navigation to proceed.

onKeyUp

Y

Key-up event anywhere within the grid. Not fired unless the grid is configured to get input focus, either explicitly via allowFocus, or implicitly because you turn on clipboard or keyboardNavigation.

onResize

N

Called when the grid has been resized because the size of its container has changed. Your handler function must return true to allow normal resizing to continue. If you return false, you should make a subsequent call to Refresh().

onRowClick

Y

Called when the user clicks anywhere in a data row. Your function must return true to allow row selection to be processed.

onRowCreate

N

Called when the grid has created the HTML for a data or group row. The context contains an element which is the row HTML. Note that a single visible row consists of separate HTML row elements in the fixed-left, central, and fixed-right areas of the grid (as applicable). This event is fired separately for each HTML row element which needs to be created; therefore, potentially more than once for each row of data.

onRowDrag

N

Called on completion of a valid row drag. Your function must return true to allow the drag to proceed. The context object contains moveRow and ontoRow properties.

onRowSelectionChange

Y

Called when the user changes the selected rows in the grid. The context object contains a changedRows array listing all items whose status is changing, plus separate selected and deselected lists.

onScroll

Y

Called when the central data area of the grid is scrolled, but before the grid is updated with any new rows which become visible.

onSort

N

Completely overrides the grid's own sorting. Can be used to suppress the built-in sorting if you are sorting the data externally, e.g. because of paging.

If you want to process the sort yourself rather than just suppressing it, you will typically need to look at the grouping and currentSort property of the grid, and process the data array contained in the context object. You modify the data array by reference (rather than e.g. returning it from your function), setting it into sorted order. An onSort function can also return true to allow the grid's normal sorting to be applied.

onSortChange

Y

Called when the user changes the sorting by clicking on a column header. Your function must return true to allow the grid to update itself.

onStateChange

N

Called when any aspect of the user-controlled state of the grid changes. You can use this function as a trigger to store grid state for future retrieval.

onStylesheetCreated

N

Called when the grid has updated the stylesheet which it uses to set column widths and other shared grid properties.

5.5Grid methods

In all the following functions, parameters which specify row indexes - documented as rowOrKey - can be passed into the method either as an object which contains an ID registered using rowKey, e.g. {presidentId: 45}, or the parameter can be the scalar value of the row's key, e.g. 45. For example:

var elements = grid.GetRowElements({customerId: 13487});

var elements = grid.GetRowElements(13487);

Method

Parameters

Description

AddRow()

Row (,bLeaveDirty)

Adds a new row into the grid's data. See the notes below about the optional bLeaveDirty parameter.

ChangeColumns()

columns[] (, newData[])

Replaces the current columns of the grid with the new definition, and re-renders. Can optionally also be given a new array of data to load, equivalent to doing a ChangeColumns() and also a Rebind(), but more efficient than doing one re-render to change the columns and a second re-render to change the data.

ClearNode()

element

Service function which removes the contents of an HTML element. Simply an optimized alternative to innerHTML="".

CollapseAllGroups()

n/a

Collapses all groups in the grid

CollapseGroup()

groupkey

Collapses the rows for the group identified by groupkey, leaving only the group header row visible

CopyToClipboard()

[clipboardDef]

Copies the current row or cell selection to the clipboard. Returns false if there is no current selection, or if the copy fails. A return value of true is not necessarily reliable because of browser security. You typically need to call this function from a mouse or keyboard event handler; browsers will generally block access to the clipboard unless it is user-initiated. Can optionally take a clipboardDef parameter describing options for doing the copy; possible values are the same as for the grid's clipboard configuration.

CreateStylesheet()

n/a

Forces the grid to regenerate its stylesheet which defines column sizes and formatting. Broadly equivalent to the results of a resize of the page/grid. Note: much more lightweight than a Refresh() or a Rebind().

CssClassChange()

element, addClasses, removeClasses

Service function for adding and/or removing CSS classes from element(s). The element parameter can be an individual node, the ID of a node, an array of nodes, or an HTML collection of nodes. The addClasses and removeClasses lists can be arrays, or a space-delimited string of class names, or null. For example:

CssClassChange(elem1,"add-class");

CssClassChange(elem2, null, "remove-class");

CssClassToggle()

element, booleanCondition, addRemove

Wrapper around CssClassChange() which adds or removes classes on an element based on a boolean condition. Equivalent to either CssClassChange(element, addRemove, null) or CssClassChange(element, null, addRemove)

DoesRowExist()

rowOrKey

Tests for the existence of a row in the grid's data; equivalent to GetRow(rowOrKey) != null. Note that this applies to the grid's complete unfiltered data.

ExpandAllGroups()

n/a

Expands all groups in the grid

ExpandGroup()

groupkey

Expands the records for the group identified by groupkey, making the rows visible

FormatCellValue()

format, value

Applies the grid's standard number or date formatting to a value. The format is an object with the same specification used for a column's format, such as {type: "date"}

GetCellByPageXY

x, y

Gets the data cell in the grid at pixel position x,y relative to the page viewport, or null if none. Returns an object with row and column properties, specifying the row data and column object.

GetCellElement()

rowOrKey, columnOrId

Gets the outer HTML element for a data cell.

GetColumnById()

id

Returns the column object for a given ID.

GetColumnByIndex()

index

Returns the object for a column using its zero-based display order within the grid. Shortcut for GetColumns()[index].

GetColumns()

n/a

Returns an array of the columns in the grid.

GetData()

n/a

Returns the sorted, filtered, and grouped data for the grid, including rows representing any group headers. Can also be referenced using the grid's data property.

GetDataRows()

n/a

Returns the sorted, filtered, and grouped data for the grid, but excluding rows representing any group headers, and therefore only returning individual data rows.

GetEffectiveRowHeight()

row | index

Gets the effective rendered height in pixels of a row, depending e.g. on whether hasIndividualRowHeights is turned on. Different to other methods because the parameter can be either the object for a row, or the zero-based display index within the grid.

GetGroups()

n/a

Returns an array of all the group identifiers in the group. If the grid is not grouped, then the array will be empty.

GetRawData()

n/a

Returns the raw data for the grid before any filtering, grouping, and sorting

GetRow()

rowOrKey

Returns a single row of data

GetRowByIndex()

index

Gets the data for a row using its zero-based display order in the grid. Shortcut for GetData()[index].

GetRowElements()

rowOrKey

Gets the HTML elements for a row - always returning an array, because there are separate row elements for fixed and unfixed areas of the grid.

GetRowsInViewport()

n/a

Returns an array of the data rows which are visible in the scrolling area of the grid, i.e. excludes any rows which are off the top or bottom of the scrolled area. Does not return rows representing any visible group headers.

GetScrollPosition()

n/a

Returns an object describing the grid's current scroll position, in pixels, containing scrollLeft and scrollTop properties. Simply reads the scroll position on the grid's htmlDataCentral element.

GetSelectedCells()

n/a

Returns an object describing the currently selected cell or range (only applicable if using a cell selectionMode). Returns the same object as the onCellSelectionChange event.

GetSelectedRows()

n/a

Returns an array of the currently selected rows in the grid

GetState()

n/a

Returns an object describing the user-controllable state in the grid: sorting, column width and position etc. You can store and retrieve this state, and load it back into the grid as an optional second parameter for the FXB.ui.Grid() constructor.

InsertRow()

Row, atDisplayIndex (,bLeaveDirty)

Inserts a new row into the grid's data, before the item currently at zero-based display order N. See the notes below about the optional bLeaveDirty parameter. If sorting is applied to the grid then InsertRow() in effect becomes identical toAddRow().

ReApplyFilters()

n/a

Tells the grid to reapply filtering. Mainly useful in relation to custom filtering.

Rebind()

data[]

Loads a new array of data into the grid. See also refreshDelayMS.

Refresh()

n/a

Completely re-renders the contents of the grid. Note: CreateStylesheet() is a more lightweight alternative if the data is unchanged, and the requirement for an update is solely in order to refresh column attributes such as width and visibility. See also refreshDelayMS.

RemoveRow()

rowOrKey (,bLeaveDirty)

Removes a row from the grid. See the notes below about the optional bLeaveDirty parameter.

ResetScroll()

n/a

Resets the scrolling of the grid to the top left corner

ScrollRowIntoView()

rowOrKey

Scrolls the grid viewport so that the specified row is visible.

SetGrouping()

columnOrId

Changes the grouping on the grid, setting it to null or to the ID of the field to group by.

SetIgnoreFilters()

bOnOrOff

Turns the ignoreFilters property on or off, and refreshes the grid

SetIgnoreGrouping()

bOnOrOff

Turns the ignoreGrouping property on or off, and refreshes the grid

SetScrollPosition()

position | left,top

Sets the grid's scroll position in pixels; simply sets the scroll properties of the grid's htmlDataCentral element. The parameters for the function can be left and top values, or a single object with scrollLeft and scrollTop properties like the return value from GetScrollPosition(). Note that the scroll position of the grid can also be set on initialization.

SetSelectedCell()

[rowOrKey, columnOrId]

Only applicable if the grid selectionMode is one of the cell modes. Sets or clears the selected cell. To clear the current selection, call the method with no parameters or null parameters. To select a cell, pass in a row and column identifier.

SetSelectedCells()

[range]

Only applicable if selectionMode is cellrange. Sets or clears the selected range of cells. To clear the current selection, call the method with no parameters. To select a range, pass in an object which identifies the start and end row and column via their zero-based display indices. For example:

{fromRowIndex: 7,

toRowIndex: 10,

fromColumnIndex: 2,

toColumnIndex: 4}

SetSelectedRows()

arr, [bAdd]

Sets the selected rows in the grid. Each item in the array can either be a row object or the key which uniquely identifies a row. Passing null or an empty array clears the selections. There is an optional second boolean parameter which specifies that the rows should be added to any current selection, rather than replacing the current selection.

SetSelectionMode()

newMode

Changes the selectionMode of the grid. Does not enforce consistency. For example, if there are currently two selected rows, then changing the mode to single will not remove one of the selections. You can do this yourself, if necessary, as illustrated by the example which is included with the grid.

SetSort()

sortdef

Changes the sorting on the grid. The sortdef can be one of four things (as returned by currentSort):

null, to turn off sorting

The string ID of a field, e.g. "firstname"

An object describing both field and direction, in the form {field: "firstname", direction: "desc"}

An array of strings or of objects, for multi-column sort

ShowFilterBar()

bOnOrOff

Shows or hides the grid filter bar

ShowFooters()

bOnOrOff

Shows or hides the grid footers

ShowHeaders()

bOnOrOff

Shows or hides column headers

UpdateCell()

rowOrKey, columnOrId, value (,bLeaveDirty)

Updates the value of a cell and re-renders it. See the notes below about the optional bLeaveDirty parameter.

UpdateRow()

Row (,bLeaveDirty)

Updates multiple values in a row. The new Row object does not have to re-state any values which are not changing; it only needs to include the row key and any properties which need to be updated. It can also be used to change the following row metadata: __height, __cssClass, __unselectable, __undraggable, __prioritySort. See the notes below about the optional bLeaveDirty parameter.

5.4.1Updating data and checking the need for a refresh

Changing the grid data, using AddRow(), RemoveRow(), UpdateCell(), or UpdateRow(), can be a relatively expensive procedure because filtering or sorting on the grid can mean that the contents then need to be substantially modified, rather than just changing a single cell/row. If you are going to make several changes to the grid data at once then it is best to do the following:

Pass true for the optional bLeaveDirty parameter. This tells the grid to update the cell/row, but not to reapply any sorting or filtering.

When you have finished your series of updates, check the value of the grid's isDirty property. If it is true, then use the Refresh() method to make the grid's sorting and filtering consistent with the changed values.

This is much more efficient than triggering a re-sort and/or re-filter on each individual change. For example:

grid.UpdateCell(123, "price", 100, true);

grid.UpdateCell(456, "price", 150, true);

grid.UpdateRow({itemId: 789, price: 180, quantity: 42}, true);

if (grid.isDirty) grid.Refresh(); // At worst one single re-sort/re-filter, rather than 3

Addition or deletion of rows will always require a refresh. The only effect of the bLeaveDirty parameter is to defer a refresh while you make multiple additions or deletions.

6.1Column configuration

Property

Description

align

Text alignment for the column, defaulting to left. Columns can have a separate headerAlign.

canGroup

Indicates that the user is allowed to group by the column

clipboardText

Optionally specifies an alternative or bare version of the column's text to use when copying to the clipboard with headers:true

cssClass

The grid automatically assigns a cssClass to each column in the grid. This is applied to all elements for the column: header, data cells, footer, and filter. You can specify your own cssClass rather than letting the grid generate one, but this value must be unique for each column in the grid. It is potentially safer to use cssAddClass.

cssAddClass

Optional custom CSS class (or space-separated list of classes) to be applied to all cells in the column. Equivalent to configuring all of cssDataClass, cssHeaderClass, cssFilterClass, and cssFooterClass.

cssDataClass

Optional custom CSS class (or space-separated list of classes) to be applied to data cells in the column

cssHeaderClass

Optional custom CSS class (or space-separated list of classes) to be applied to the header cell in the column

cssFilterClass

Optional custom CSS class (or space-separated list of classes) to be applied to the filter cell in the column

cssFooterClass

Optional custom CSS class (or space-separated list of classes) to be applied to the footer cell in the column

draggable

Indicates that the user is allowed to drag the column to a new position. If omitted, defaults to the grid's allColumnsDraggable. Columns cannot be moved out of or into fixed areas; they can only be dragged within their own "group".

fill

Indicates that the column should be expanded to fill any spare space in the grid. If there is no spare space - i.e. other columns are wide enough to fill the grid, and the "spare" space is negative - then the standard width or onGetWidth is applied. See also spacer. Only one column in a grid should be marked as either fill or spacer.

filter

Type of filtering to allow on the column. Permitted values are list, number, textstart, textexact, and textcontains. A textexact match is case-sensitive; other textual matches are not. A list requires you to specify the contents of the list using filterOptions. To implement your own type of custom filtering, you need to use a filterRenderer to create the UI for the filter, and an onFilterCompare tell the grid whether each row matches your filter.

filterCondition

Initial value for pre-populating a filter field. Simply equivalent to what a user types into the field. For filter:list it should be a value from the array in filterOptions. This property is updated by the grid when the user changes the value of a UI filtering field.

filterOptions

Only applicable, and compulsory, for filter:list. An array of options to display in the drop-down filter control, with each option defined as {value: X, text: Y}

fixed

Boolean indicating that the column should be fixed to the left or right of the grid. If a fixed column occurs before any unfixed columns in the array then it is fixed to the left. If it is listed after unfixed columns, then it is fixed to the right.

footer

Footer summary operation to carry out on the cells in the column. Possible values are sum, avg, min, max, and count. You can do a bespoke calculation by implementing a footerRenderer.

format

Object containing a type property, specifying how the grid should format the cell. Can optionally include further properties which qualify how the cell is displayed. There are three types of format specification:

· Images: type:"image" or type:"backgroundImage"

· Hyperlinks: type:{"link"}

Number or date formatting, where type is number, cash, or date

For full control over how a column's values are displayed, use a cellRenderer.

headerAlign

Optional separate alignment for the column header, differing from the main data align.

hidden

Boolean marking the column as hidden. Same effect as setting the width to zero.

hideDivider

If header divider markers are turned on, tells the grid to suppress the marker for this column (by adding .fxbgrid-hide-divider to the header cell's container)

id

ID of the column, telling the grid what property to use from each row of data. It is strongly recommended that all columns have an ID even if there is no corresponding value in the data (e.g. for a spacer, or an icon column).

keepContent

If (a) you are using a cellRenderer, and (b) you are updating data using UpdateRow() or UpdateCell(), then you may want to turn on keepContent. By default, the grid will empty a data cell before calling your cellRenderer. If may be more efficient if your renderer can instead update previous content rather than completely re-rendering the cell. The keepContent setting tells the grid not to clear the cell before calling your renderer.

maxWidth

Maximum width, in pixels, for the column. Only applicable if the width is specified as a percentage.

minWidth

Minimum width, in pixels, for the column. Only applicable if the width is specified as a percentage.

resizable

Indicates that the user can resize the column. If omitted, then it defaults to the grid's allColumnsResizable

rowDrag

Indicates that the column should be treated as a drag handle for re-ordering rows

sortable

Either a boolean indicating that the user is allowed to sort the column - sortable:true - or the specifier sortable:"text" or sortable:"number" hinting to the grid how to do the sort (if no custom onSortCompare is specified). Text sort is case-insensitive. If sortable:true is used, then the default Javascript comparison is used, making it a case-sensitive sort.

sortDefaultDirection

Can be set to desc to change the default sort direction, when the user clicks on the column, from ascending to descending. Does not prevent the user clicking a second time to invert the direction to ascending; see sortFixedDirection.

sortFixedDirection

Can be set to asc or desc to make the sort order on the column fixed. If the user clicks on the column a second time, the sort order does not change.

spacer

Indicates that the column should be expanded to fill the grid, but different to fill:true because the column is hidden if it is not needed - i.e. if the other columns are wide enough to fill the grid. Usually used for an empty column with no data associated with it.

text

The text to display in the column header

width

Width for the column, defaulting to the grid's defaultColumnWidth, and overridden by any onGetWidth handler. Either a number of pixels, or a percentage of the container width as a string in the form "12%". If a percentage, then it can be capped and collared by minWidth and maxWidth values.

6.2Column properties

Once the grid is created, the object representing each column gains some further properties in addition to the initial configuration described above.

Property

Description

area

Area to which the column belongs: central for a normal column, or left or right for fixed columns.

displayOrder

Zero-based current display order of the column within the grid.

effectiveWidth

Current rendered width of the column, in pixels.

6.3Column renderer functions

Renderer functions are called when the grid wants to create the HTML for some part of a column: its header, a data cell etc. You can implement handlers for these hooks in order to change the HTML of the grid, or attach event handlers to HTML elements etc.

If you define a renderer function then it replaces the grid's standard HTML creation - i.e. replaces rather than giving you the opportunity to modify the standard HTML.

Alternatively, you can also handle "post-rendering" events in the grid such as onDataCellRender and onRowCreate. The grid creates its standard HTML, and you can then modify that HTML in post-rendering, doing things such as adding event handlers into elements.

All renderer functions are called with a context object. This always contains the following properties:

Property

Description

grid

Reference to the grid object

column

Reference to the column object

element

The HTML element into which you should put the cell content

The renderer functions which you can define for a column are as follows:

Renderer

Description

cellRenderer

Used to render a data cell, as in the examples above for images and hyperlinks. In addition to the standard properties, the context object contains a row object and the raw value for the cell (which will be the same as row[column.id]). The element in the context object is the cell's inner container. If you are update data using UpdateRow() or UpdateCell(), then you may want to turn on keepContent.

filterRenderer

Used to render the filtering cell for a column. Can be used to implement a custom filtering UI for a column, as in the example above.

footerRenderer

Used to render the footer/summary cell for a column. Can be used to implement custom summary operations, as in the example above, or to re-use the summary row to display hyperlinks etc. The context object contains the full data array for the grid.

groupHeaderRenderer

Used to render the value of a group header row. The grouping value is in context.value (and also in context.row.__group). If no groupHeaderRenderer handler is defined, then the grid will use any cellRenderer for the column.

headerRenderer

Used to render the header text for a column. (Is not responsible for rendering sort icons or the column resizer; only handles the text displaying the column title.)

headerRendererOuter

Lets you override the complete header cell for a column, not just its text. If you implement this renderer then the context object's element is the bare outer container for the header, and you have complete control over creation of sort icons, group icons, text etc. However, you are then also responsible for setting up all required event handlers, and for then changing the grid properties using methods such as SetSort() and SetGrouping(). This renderer is exceptional because you do have the option of letting the grid continue to create its standard HTML; returning true from your function allows normal rendering to proceed.

6.4Column events

Browser events such as clicks are notified via the grid events, not via individual columns. For example, when the user clicks on a column header, that is fired as the grid's onHeaderClick, not as an event on the column. Therefore, all the "column events" which the grid fires are to give you an opportunity to do things such as overriding sort comparisons for the column.

All column event handlers receive a context object as a parameter, and this always contains grid and column properties. Some event handlers receive extra data in their context object, as described below.

Property

Description

onFilterCompare

Called in order to handle comparison of a custom filter against a data row. The context object contains the current condition data for the filter; the row value to compare against it; and also the full row data. Your function should return true if the row data matches your filter condition.

onGetFilteringValue

Lets you override the value used for each row when comparing it to a filter (without using onFilterCompare and thus having to completely reimplement the filtering comparison). The context object contains the row to be filtered, and the defaultValue which the grid would normally use - which will be row[column.id].

onGetGroupingValue

Lets you override the value used for each row when grouping by a column. You can also or alternatively override the display of the grouped value using the column's groupHeaderRenderer event. The context object contains the row to be grouped, and the defaultValue which the grid would normally select for the grouping - which will be row[column.id]. If your function returns null, the grid will use the default value.

onGetWidth

Lets you calculate a custom width for a column whenever the grid is resized - see the edit-mode example. The context object contains the current width of the grid's container, in containerWidth. Your function should return a value in pixels, including zero to hide the column, or can return null to allow standard column sizing to be used.

onSortCompare

Lets you override the standard sort comparison for a column. The context object contains the individual valueA and valueB to be compared, and also the full row data in rowA and rowB. Your function should return -1 if A should be sorted before B; 1 if A should be sorted after B; or zero if the values match. Your function can also return null to allow the grid to use its default sort comparison. Your function does not implement ascending/descending order. You always sort in ascending order, and the grid inverts your result if the user has chosen descending order.

6.5Column methods

The object representing a column can be obtained using the grid's GetColumnById() or GetColumnByIndex() methods. Column objects only have two simple methods. To make any other changes to columns, reload a new set of columns into the grid using its ChangeColumns() method.

Method

Parameters

Description

SetWidth()

width

Changes the column width. As in the original column definition, width can be either a number of pixels or a percentage in the form of a string such as "12%"

Show()

bOnOff

Shows or hides a column

7Examples of extending the grid

All the topics in this section - plus many others - are illustrated in full by the example files which are included with the grid.

7.1Cell editing

The FXB grid does not provide built-in cell editing. It's easy to add this, but the grid's philosophy is that all the parsing, UI design, and localization associated with data entry should be implemented outside the grid, rather than complicating the grid's configuration and dependencies.

The simplest way of adding cell editing, as illustrated by the example included with the grid, is as follows:

Handle onCellClick

When a cell is clicked on, create an input UI in the cell such as a <select> or <input>. (You can either replace the normal cell contents, or add an element which is rendered over the top of the cell display using a higher z-index.)

If the user enters a new value, use UpdateCell() to change the grid data. This will re-render the grid and destroy the input UI.

If the user dismisses the input UI, re-render the grid to restore the normal cell contents.

7.2Paging

The FXB grid does not have built-in paging of data because the grid doesn't interfere with your collection of data and server communication. It's simple to add paging yourself - provided that you are not turning on the grid's filtering or grouping functionality.

However, it's first worth considering that grids typically have paging for one or more of the following three reasons:

1. The performance of the grid control degrades badly with large datasets.

2. A paged grid is more convenient to use than a long scroll.

3. Downloading all the data for the grid is too demanding compared to requesting smaller pages from a server.

The FXB grid can handle large numbers of records without impairing performance. That strikes out most instances of factor (1).

With large datasets, a user will typically want to see "first N sorted by X", or to filter/search the values, rather than wanting to page through the entire grid. The FXB grid's facilities for sorting and filtering should eliminate many instances of factor (2).

That mostly leaves downloading large quantities of data from a server as the reason for needing paging - though "infinite"/virtual scroll may be a more user-friendly alternative to paging.

In order to implement paging, you need to do the following:

Implement your own HTML paging bar

Listen for the grid's onSortChange event, loading in a revised page of data based on the new sorting

Handle the grid's onSort event, simply suppressing the built-in sort, e.g. using onSort: function() {return false;}, because you will need to sort any data externally, on your server.

When the user clicks on your paging bar or when onSortChange is fired, you assemble the corresponding page of records and load the data into the grid using Rebind().

This becomes substantially harder if you also want to use the grid's filtering. You may find it easier to build your own filtering options outside the grid. To integrate with the grid's filtering, you need to do the following:

Set the special ignoreFilters property which tells the grid that it should let the user enter filter values, but not actually apply them to the data - because you will be doing this.

Handle the onFilterChange event which tells you when the user alters one of the column filters.

When assembling a page of data (because of filtering, sorting, or clicks on your paging bar), you need to read the filterCondition value for each column and pass it to the server. The server then needs to replicate the grid's filtering so that it only returns page N of the filtered records, not of the entire dataset.

7.3Infinite scrolling / lazy loading

An alternative to paging is "infinite scroll" or "lazy loading" - loading data in pages/batches on demand, when the user scrolls down the grid. One way of implementing this is the following technique:

Add dummy rows into the bottom of the data which you load into the grid

When the grid is scrolled, use GetRowsInViewport() to check whether any of these dummy rows have become visible and, if so, load more data from the server and then Rebind().

For example… we use two data arrays. The actualData array holds the total data collected from the server so far. The dataWithPlaceholders array holds the actual data plus the addition of some dummy placeholder rows, consisting only of {placeholder:true}. We create the grid, hook into a couple of events, and load some initial data:

var grid; // Grid which is created in page onLoad

var actualData = []; // The actual data loaded from the server so far

var dataWithPlaceholders = []; // Actual data + dummy placeholder rows

var outOfData = false; // Flag indicating that no more data is available from the server

var isBusy = false; // Prevent overlapping calls to the server

window.addEventListener("load", function() {

// Create the grid, initially with no data

grid = new FXB.ui.Grid({

container: "MyGridContainer",

columns: [{id: "name", text: "Name"}],

data: [],

pagesLoaded: 0 // Use a custom property to record pages loaded so far

});

// Listen to onScroll and also to onGridRender.

grid.onGridRender = CheckForNeedNewData;

grid.onScroll = CheckForNeedNewData;

// On initial load, call a function which loads a page of data, using

// a zero-based page index

GetPageOfData(0, function() {

// ... Called back asynchronously when the data is loaded

// Set the count of pages loaded so far

grid.pagesLoaded = 1;

// GetPageOfData() maintains the dataWithPlaceholders array.

// We simply Rebind() it into the grid.

grid.Rebind(dataWithPlaceholders);

});

});

We then handle onScroll using the following function. If the visible rows in the grid include placeholders, we need to load new data. We also point onGridRender at this function, in case the initial page which is loaded is not enough to fill the visible grid, and we immediately need to load more pages.

function CheckForNeedNewData()

{

// Exit early if we're already in the middle of data collection from the server,

// or if we know that no more data is available from the server

if (!grid || isBusy || outOfData) return;

// See if there are any dummy placeholder rows visible within the grid

var visibleRows = grid.GetRowsInViewport();

var bNeedNextPage = false;

for (var i = 0; i < visibleRows.length; i++) {

if (visibleRows[i].placeholder) {

bNeedNextPage = true;

break;

}

}

// If any dummy placeholder rows are visible, load the next page of data

if (bNeedNextPage) {

GetPageOfData(grid.pagesLoaded, function() {

grid.pagesLoaded++;

grid.Rebind(dataWithPlaceholders);

});

}

}

An example of the function which collects a page of data from the server, GetPageOfData(), then looks like this. This example uses setTimeout to simulate asynchronous loading from a server, and builds simulated data up to a maximum number of pages.

// Does an asynchronous callback into the function provided by the caller

function GetPageOfData(pageIndex, callback)

{

// Prevent overlapping calls to the server

isBusy = true;

// Simulate asynchronous loading of data from a server using setTimeout

setTimeout(function() {

isBusy = false;

// Build simulated data as though returned from a server

var newData = []; // Remains empty if we've loaded all available data

var maxSimulatedPages = 10;

var simulatedPageSize = 100;

if (pageIndex < maxSimulatedPages) {

for (var i = 0; i < simulatedPageSize; i++) {

var recordId = (pageIndex * simulatedPageSize) + i;

newData.push({recordId: recordId, name: "Record " + recordId});

}

}

if (newData.length) {

// If the server has provided some new data, we do two things.

// 1. Combine the new data with the actualData so far

// 2. Rebuild dataWithPlaceholders with dummy placeholder rows

actualData = actualData.concat(newData);

dataWithPlaceholders = actualData.slice(0);

// Put an arbitrary number of placeholder rows into dataWithPlaceholders,

// detected by CheckNeedForNewData(), using the size of the server's

// latest data data as the number of placeholders to add

for (var i = 0; i < newData.length; i++) {

dataWithPlaceholders.push({placeholder: true});

}

} else {

// If the server has run out of data, then simply set dataWithPlaceholders

// to be actualData. Can also set a flag which tells CheckForNeedNewData

// that it can exit early, without looking for placeholders which won't exist.

dataWithPlaceholders = actualData.slice(0);

outOfData = true;

}

// Do the callback

(callback)();

}, 1000); // Simulated 1-second delay on asynchronous data loading

}

If simulatedPageSize is set to something small - e.g. 5 records - which is not enough to fill the visible area of the grid, then the fact that we hook onGridRender means that pages will be repeatedly loaded until the grid is filled (or until we run out of data).

If the total number of available rows on the server is known at outset, then GetPageOfData() could create an accurate number of placeholder rows rather than an arbitrary number, making the size/length of the scrollbar representative of the actually available amount of data.

As with paging, you need to handle any sorting of the data yourself, outside the grid:

Listen for the grid's onSortChange event. In this example you would reset grid.pagesLoaded, wipe the two data arrays, and start loading pages again from page 0. You would also typically turn on resetScrollOnSort.

Handle the grid's onSort event, simply suppressing the built-in sort, e.g. using onSort: function() {return false;}, because you are sorting the data externally.

In GetPageOfData(), you examine the grid's currentSort, and pass that information to the server so that it provides you a page of records based on the select sort criteria.

7.4Frozen rows at the top or bottom of the grid

You can use a __prioritySort value in a row's metadata to force rows to be sorted at the top or bottom of the grid, taking precedence over any normal sort. However, these rows can be scrolled out of view. This does not handle "frozen" rows which should be permanently visible outside the scrolling area.

You can implement frozen rows using two (or three) separate grids. The following example describes how to implement frozen rows at the top of the grid. Frozen rows at the bottom are a simple extension of that.

You create two grids with identical column configurations, above one another. The top one has hideHeaders:false and the bottom one has hideHeaders:true. Therefore, the rows in the top grid appear to be part of the same block as the rows in the bottom grid, because nothing separates them. You load the data for frozen rows into the top grid, and the rest of the data into the bottom grid.

When the sorting is changed on the top grid (or columns are resized etc) you need to apply the same change manually to the bottom grid. For example, you listen to the top grid's onSortChange or onStateChange events, and apply the change to the bottom grid using a method such as SetSort().

8.1Browser compatibility

The grid is principally tested with Chrome and Safari. There is slightly more limited testing on Firefox, Opera, Edge, and Internet Explorer (v11 only).

The grid contains no variant per-browser tweaks apart from a couple of definitions of unstandardized CSS attributes such as user-select and -ms-user-select. Therefore, it should work with all browsers that adhere to official and de-facto standards.

8.2Can I get the un-minified Javascript for the grid?

In short, no. If you work for NASA or the military, and you need to validate the grid for use in potentially life-threatening situations, then we might treat that as an exception. But, otherwise, no. The grid has so many extension points that the un-minified source code wouldn't open up any additional possibilities.

8.3Does the grid "phone home"?

No, the grid does not "phone home" to check its licensing. You can safely use it in contexts such as Cordova apps, and you aren't inadvertently disclosing usage figures and activity for your site/app by including the grid in your project.

8.4Why is my <p> or <div> not visible in a cell?

If you are adding a block element such as <p> or <div> into a cell, using rendering or post-rendering, then it may be invisible because of the way the grid handles vertical centering.