ZingGrid is a feature-rich JavaScript library for visualizing and interacting with tabular data. In this article we'll see how to get started with ZinGrid and Svelte and look at tapping into some ZingGrid features from within a Svelte app. This is not meant to be an exhaustive review of ZingGrid's capabilities, to learn more about what ZingGrid can do visit the ZingGrid home page. The GitHub repository corresponding with this article is here.
ZingGrid is written in vanilla JavaScript and can be used in any web application, with or without a framework. Since it's implemented as a standard web component, no wrapper is needed to incorporate it into a Svelte app. We'll start out with Svelte template and quickly get a static grid displayed. Then we'll look at each of the pages in the demo and highlight how to integrate ZingGrid features like themes, events, and data binding within a Svelte context. We won't build the entire demo app in this article, so I recommend that you download the demo and start it up as a reference.
Hello World
First, though, let's create a Hello World ZingGrid/Svelte app. We'll start by creating a new Svelte app, installing the ZingGrid component, and starting the app.
npx degit sveltejs/template zinggrid-svelte-helloworld
cd zinggrid-svelte-helloworld
npm install
npm install zinggrid
npm run dev
You can now view the app in a browser at localhost:8080
(check the output of npm run dev
, it might be running on a different port).
The full demo contains 11 pages demonstrating different aspects of using ZingGrid in Svelte. For this Hello World
example, we'll just add a static grid to the App.svelte
page.
Let's start by adding the import for ZingGrid. We'll also remove the name
component property:
zinggrid-svelte-helloworld/src/App.svelte
<script>
import 'zinggrid'
</script>
Now remove the contents of <main>
and replace them with a zing-grid
component:
zinggrid-svelte-helloworld/src/App.svelte
<main>
<zing-grid />
</main>
In the browser you should now see an empty grid, showing the text There are no records to display.
and the Powered by ZingGrid
logo. Let's define some tabular data. In the <script>
section, add an array of objects called data
. Note that each object in the array contains the same name
and origin
properties, these will become the columns of the grid:
zinggrid-svelte-helloworld/src/App.svelte
<script>
import 'zinggrid'
const data = [
{name: "Philip J. Fry", origin: "Earth"},
{name: "Turanga Leela", origin: "Earth"},
{name: "Bender Bending Rodriguez", origin: "Earth"},
{name: "Amy Wong", origin: "Mars"},
{name: "Doctor John Zoidberg", origin: "Decapod 10"},
{name: "Lord Nibbler", origin: "Earth"}
]
</script>
Change the zing-grid
component to look like this:
zinggrid-svelte-helloworld/src/App.svelte
<main>
<zing-grid caption="Hello Futurama" data={JSON.stringify(data)} />
</main>
Note that we've added two attributes to the zing-grid
component: caption
and data
. ZingGrid is expecting a JSON string as the value for data
, so we do the conversion to JSON in the component definition (alternatively, we could have chosen to JSON.stringify
the data
array in the <script>
section).
You should now see a grid in the browser! Note that the text in the grid is centered, this is because of the text-align: center;
CSS styling applied to <main>
in the <style>
section. We could fix this by deleting that line, but, since ZingGrid is a standard web component, we can just style it with CSS:
zinggrid-svelte-helloworld/src/App.svelte
<style>
...
zing-grid {
text-align: left;
}
...
</style>
This app can be used to play around with all of the different grid options described in the ZingGrid docs. Let's move on from here to look at various aspects of using ZingGrid features in a Svelte app.
zinggrid-svelte-demo
For this part of the article you should have the demo running locally. I'll hightlight key sections of the source code with a focus on the interaction between Svelte and ZingGrid.
In an environment with nodejs
, npm
, and git
installed:
git clone https://github.com/zinggrid-demos/zinggrid-svelte-demo.git cd zinggrid-svelte-demo npm install npm run start
You should now be able to view the demo in your browser at localhost:5000
. Each of the demo pages is contained in a file under zinggrid-svelte-demo/src/demos
.
Hello World (zinggrid-svelte-demo/src/demos/Simple.svelte
)
The Hello World
demo is nearly identical to the simple example we created above in zinggrid-svelte-helloworld/src/App.svelte
. There are a few minor differences:
- We've added the
head-class
attribute to thezing-grid
component. This attaches the CSS classgrid-header
(defined inzinggrid-svelte-demo/public/global.css
) to the header, making it appear in a bold font. - The component is no longer wrapped in a
<main>
component, it's wrapped in a<div>
that's styled in theglobal.css
file.
Themes (zinggrid-svelte-demo/src/demos/Themes.svelte
)
This demo shows how to use a Svelte reference to a ZingGrid in order to set the data and modify the grid's visual theme after it has been created. We start by declaring a component variable grid
to hold the reference and another, theme
, to hold the grid's theme. We then declare the table data, in this case as an array of arrays, one per row:
zinggrid-svelte-demo/src/demos/Themes.svelte
let grid
let theme = 'default'
const data = [
[1,2,3],
[4,5,6]
]
Here's the portion of the HTML for this component that defines the grid (the rest of the HTML creates the menu for selecting the theme):
zinggrid-svelte-demo/src/demos/Themes.svelte
<zing-grid bind:this={grid} caption="Hello World" theme={theme} head-class="grid-header">
<zg-colgroup>
<zg-column index="0" header="Column 1"></zg-column>
<zg-column index="1" header="Column 2"></zg-column>
<zg-column index="2" header="Column 3"></zg-column>
</zg-colgroup>
</zing-grid>
The reference to this grid is bound using the bind:this
attribute to zing-grid
and the theme is set using the theme
attribute. The contents of this zing-grid
consist of a zg-colgroup
comprised of zg-column
components that set the header for each column and its index into each of the rows of the data array.
When we obtain a reference to a ZingGrid component, all of the methods provided by the ZingGrid API are available, attached to the reference. In this case we use Svelte's onMount
function to invoke grid.setData(data)
to set the table data for the grid. Note that when using setData
we don't have to encode the data as JSON, as we do when using the data
attribute.
The select menu:
zinggrid-svelte-demo/src/demos/Themes.svelte
<select value={theme} on:change={ev => theme = ev.target.value}>
<option value="default">Default</option>
<option value="android">Android</option>
<option value="ios">iOS</option>
<option value="dark">Dark</option>
<option value="black">Black</option>
</select>
lists the theme options available in ZingGrid. The on:change
handler sets theme
to the selected theme name, which causes Svelte to rerender the grid with the chosen theme.
Methods (zinggrid-svelte-demo/src/demos/Methods.svelte
)
The Methods demo further explores the use of ZingGrid methods, in this case allowing the editing of the table data via an HTML textarea
.
We start by declaring two Svelte references for the grid and textarea and the initial table data. Note that this data array consists of objects, each of which has breed
and name
properties.
zinggrid-svelte-demo/src/demos/Methods.svelte
let grid = null
let textarea = null
const dataSet = [
{ "breed": "Chow Chow", "name": "Butter"},
{ "breed": "Dachshund", "name": "Sousage"},
{ "breed": "Pug", "name": "Potat"},
{ "breed": "Corgi", "name": "Plop"},
{ "breed": "Pomeranian", "name": "Floof"}
]
The HTML for this component creates the ZingGrid, textarea, and a button, binding the references to the grid and textarea and specifying the handler for the button:
zinggrid-svelte-demo/src/demos/Methods.svelte
<div class="GridSidebar-wrapper">
<zing-grid
bind:this={grid}
editor
pager={true}
head-class="grid-header"
/>
<div class="Sidebar-wrapper">
<br/>
<textarea bind:this={textarea} cols="50" rows="12" spellcheck="false"></textarea>
<br/>
<button on:click={setData}>Set Data</button>
</div>
</div>
The work is done by the setData
function, which gets the text from the textarea and sets it as the table data. setData
is called both when the component is mounted, via onMount
, and every time the button is pressed. The data object is edited in the textarea as JSON, which the corresponding ZingGrid API method setData
accepts.
zinggrid-svelte-demo/src/demos/Methods.svelte
const setData = () => grid.setData(textarea.value)
Events (zinggrid-svelte-demo/src/demos/Events.svelte
)
ZingGrid supports a set of events that are generated when the grid is rendered or the user interacts with it. This demo shows handling these events and logging them to a textarea.
We create two Svelte references, grid
and eventLog
, and declare an array of the names of the events we want to monitor:
zinggrid-svelte-demo/src/demos/Events.svelte
let grid = null
let eventLog = null
...
const events = ['grid:ready', 'cell:click', 'header:click', 'grid:pagechange']
In order to establish the event handler, we need to wait until the grid has been rendered. We do this using Svelte's onMount
function:
zinggrid-svelte-demo/src/demos/Events.svelte
onMount(() => {
for(const ev of events)
grid.addEventListener(ev, handleEvent)
})
Each of the events is bound to the same event listener:
zinggrid-svelte-demo/src/demos/Events.svelte
const handleEvent = (event) => {
let text = ''
const target = event.detail.ZGTarget ? event.detail.ZGTarget.localName : 'zing-grid'
text += `${event.type} from ${target}`
text += details(event.type, event.detail.ZGData)
text += '\n\n'
eventLog.value += text
eventLog.scrollTop = eventLog.scrollHeight
}
handleEvent
receives an event object as its argument. We pick out the properties we'd like to display, mostly from event.detail
, append them to the eventLog
text, and scroll to the bottom of the textarea.
Data from REST Server (zinggrid-svelte-demo/src/demos/Fetch.svelte
)
Up to now we've been displaying grids with static data stored in a JavaScript object. In this demo we'll use JavaScript's fetch
API to retrieve data dynamically. We'll be fetching from a JSON file on the application's server, but we could also fetch from a remote REST or GraphQL endpoint.
We again use a Svelte reference to the grid and Svelte's onMount
function to set the data for the grid after it has been displayed:
zinggrid-svelte-demo/src/demos/Fetch.svelte
// Fetch the dataset
async function getData() {
try {
const res = await fetch('./shows.json')
const data = await res.json()
// purposely timeout so the loading screen displays longer
await delay(2000)
grid.setData(data.shows)
} catch(err) {
console.log(err)
}
}
onMount(getData)
The fetch logic is moved into a separate function, getData
, called via onMount
. Here we await a fetch of the contents of the file ./shows.json
from the server and convert the result to JSON. We insert a 2-second delay to simulate fetching from a remote server, and then use ZingGrid's setData
API call to set the grid's data. The ZingGrid is configured to display a loading message by using the loading
and loading-text
attributes:
zinggrid-svelte-demo/src/demos/Fetch.svelte
<zing-grid bind:this={grid}
caption="Shows"
editor
head-class="grid-header"
loading={true}
loading-text="Loading (delayed by 2 seconds)">
</zing-grid>
Read Data from GraphQL Server (zinggrid-svelte-demo/src/demos/GraphQL-read.svelte
)
In the previous demo we fetched data using JavaScript, but ZingGrid can also be configured to fetch data itself from several types of servers (REST, GraphQL, Firebase, and others) using the zg-data
web component. In this demo we'll read data from a public GraphQL endpoint.
Here is how the zing-grid
component is configured:
zinggrid-svelte-demo/src/demos/GraphQL-read.svelte
<zing-grid caption="GraphQL Demo" head-class="grid-header" loading>
<zg-data src="https://countries.trevorblades.com/" adapter="graphql">
<zg-param name="recordPath" value="data.continents"></zg-param>
<zg-param name="readBody" value={readBodyJSON}></zg-param>
</zg-data>
</zing-grid>
zg-data
's src
and adapter
attributes specify that we're reading from a GraphQL server and set its URL. The contents of the zg-data
component consist of zg-param
components that control how the data is obtained. The readBody
parameter's value is a JSON encoding of an object with a query
property containing the GraphQL query:
zinggrid-svelte-demo/src/demos/GraphQL-read.svelte
const readBodyJSON = JSON.stringify({
query: `
{
continents {
name
}
}
`
})
This specifies the structure of the data we want to receive. The resulting JSON object will look like this:
{
"data": {
"continents": [
{"name": "Africa"},
{"name": "Antarctica"},
{"name": "Asia"},
{"name": "Europe"},
{"name": "North America"},
{"name": "Oceania"},
{"name": "South America"}
]
}
}
The recordPath
zg-param
configures ZingGrid to use the data.continents
property of the retrieved object as the tabular data for the grid.
GraphQL CRUD (zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
)
ZingGrid can do more than just read data from a remote source, it can be configured to allow editing of the table data and support full CRUD (Create/Read/Update/Delete) access to the data source.
To support this demo, ZingSoft hosts a GraphQL server at this URL. The data there is reset once an hour:
zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
const demoServer = 'https://examples.zingsoft.com/graphql'
Here's the definition of the ZingGrid component:
zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
<zing-grid context-menu caption="GraphQL CRUD Demo" head-class="grid-header" editor-controls>
<zg-colgroup>
<zg-column index="id" hidden editor="disabled"></zg-column>
<zg-column index="title" header="Series Title" renderer="xss"></zg-column>
<zg-column index="seasons" header="# of Seasons" type="number"></zg-column>
<zg-column index="provider" header="Provider or Channel" renderer="xss"></zg-column>
<zg-column index="genre" header="Genre" renderer="xss"></zg-column>
</zg-colgroup>
<zg-data src={demoServer} adapter="graphql">
<zg-param name="recordPath" value="data.shows"></zg-param>
<zg-param name="readBody" value={readBodyJSON}></zg-param>
<zg-param name="createBody" value={createBodyJSON}></zg-param>
<zg-param name="updateRowBody" value={updateRowBodyJSON}></zg-param>
<zg-param name="updateCellBody" value={updateCellBodyJSON}></zg-param>
<zg-param name="deleteBody" value={deleteBodyJSON}></zg-param>
</zg-data>
</zing-grid>
First, note the addition of the editor-controls
and context-menu
attributes on the zing-grid
component. editor-controls
enables controls on the table for deleting and editing rows and adding new rows. context-menu
enables a right-click popup menu that also includes some editing controls.
Skipping down to the zg-data
sub-component we see recordPath
and readBody
parameters similar to the ones from the read-only GraphQL demo. The query for reading the data is:
zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
const readBodyJSON = JSON.stringify({
query: `
query {
shows {
id
title
seasons
provider
genre
}
}`
})
This will return a table of TV shows consisting of a title, number of seasons, provider, and genre for each. We also include the database id for each entry, but we do not want to display this to the user (we certainly don't want to allow the user to edit the ids!). In the zg-colgroup
component of the grid we include a zg-column
for the id, but add the hidden
attribute so that the column does not appear in the grid, and the editor="disabled"
attribute so that, when the user adds a new row to the table, the id field is not included in the row editor.
The id becomes important for the GraphQL mutations to update and delete data, specified as the zg-param
s updateRowBody
, updateCellBody
, and deleteBody
queries. Let's look at the mutation for updating a row in the database (the same mutation is used for both updateRowBody
and updateCellBody
):
zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
const updateRowBodyJSON = JSON.stringify({
query: `
mutation {
updateShow(id:[[record.id]], title:"[[record.title]]", seasons:[[record.seasons]], provider:"[[record.provider]]", genre:"[[record.genre]]") {
id
}
}`
})
The updateShow
mutation is provided by the GraphQL server and requires the id for the database row we want to modify, along with the new title, seasons, provider, and genre values. The [[record.*]]
notations in the query text are template markers that will be filled in by ZingGrid before the query is sent to the GraphQL server. All of the fields of the edited row of the grid are available, prefixed by record.
.
Similarly, when we create a new row in the grid, the values entered into the popup form are available via [[record.*]]
template markers and used to construct the createShow
GraphQL mutation:
zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
const createBodyJSON = JSON.stringify({
query: `
mutation {
createShow(title:"[[record.title]]", seasons:[[record.seasons]], provider:"[[record.provider]]", genre:"[[record.genre]]") {
id
}
}`
})
Since the data is stored on a public server and could be vulnerable to cross-site scripting (XSS) attacks, we want to sanitize the data that's displayed. We obtain a filter function from the xss
npm
package:
zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
import {filterXSS} from 'xss'
and register this method with ZingGrid, after the grid has been displayed, so we can use it in the component:
zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
onMount(() => ZingGrid.registerMethod(filterXSS, 'xss'))
In each of the zg-column
s that contain text, we use the renderer
attribute to cause the text to be filtered by filterXSS
before it is displayed (for more on this technique, see the discussion of the Register Method demo below):
zinggrid-svelte-demo/src/demos/GraphQL-crud.svelte
...
<zg-colgroup>
<zg-column index="id" hidden editor="disabled"></zg-column>
<zg-column index="title" header="Series Title" renderer="xss"></zg-column>
<zg-column index="seasons" header="# of Seasons" type="number"></zg-column>
<zg-column index="provider" header="Provider or Channel" renderer="xss"></zg-column>
<zg-column index="genre" header="Genre" renderer="xss"></zg-column>
</zg-colgroup>
...
One-Way Binding (zinggrid-svelte-demo/src/demos/OneWay.svelte
)
This demo shows one-way binding from some HTML components to a ZingGrid. The bindings are managed via Svelte component variables, so no reference to the grid is required.
The definition of the grid incorporates several component variables:
zinggrid-svelte-demo/src/demos/OneWay.svelte
<zing-grid
caption={caption}
data={stringData}
pager={pagerOn}
theme={theme}
head-class="grid-header"
page-size="5"
page-size-options="2,5,15,25,50" />
The rest of the HTML for this component consists of a textarea and other controls that modify the state. Each modification of state causes the grid to be re-rendered. The Load Data Set
menu selects between the two datasets: an array of arrays of numbers and an array of objects describing some dog breeds. Note that, since we're setting the grid data using the data
attribute, we must JSON encode the data.
Two-Way Binding (zinggrid-svelte-demo/src/demos/TwoWay.svelte
)
The two-way binding demo combines the use of Svelte component variables as seen in the one-way binding demo with ZingGrid event handling to connect a grid to a textarea so that changes in either affect the other.
Let's look at the HTML for this component:
zinggrid-svelte-demo/src/demos/TwoWay.svelte
<div class="GridSidebar-wrapper">
<zing-grid
bind:this={grid}
caption="Two-Way Data Binding (right click on cells for context menu options)"
context-menu
editor
editor-controls
layout-controls
search
sorter
pager
page-size="5"
head-class="grid-header"
page-size-options="2,5,15"
data={JSON.stringify(tableData)} />
<div class="Sidebar-wrapper">
<p>JSON data</p>
<textarea name="ds" cols="50" rows="22" value={textData} spellCheck="false"
on:input={ev => { tableData = JSON.parse(ev.target.value); textData = ev.target.value } } />
</div>
</div>
The ZingGrid component definition includes a number of attributes that enable editing controls, the search mechanism, paging, and sorting. The data from the grid is a JSON rendering of the Svelte variable tableData
. The value for the textarea is the textData
variable, which holds a JSON rendering of tableData
. The on:change
handler for the textarea updates both the tableData
and textData
states. Updating tableData
causes the grid to be re-rendered with the new contents.
The onMount
function establishes the event handler for each of the editing events that can occur on the grid:
zinggrid-svelte-demo/src/demos/TwoWay.svelte
onMount(() => {
grid.addEventListener('data:record:delete', dataChange)
grid.addEventListener('data:record:insert', dataChange)
grid.addEventListener('data:cell:change', dataChange)
grid.addEventListener('data:record:change', dataChange)
})
The dataChange
function receives an event object containing the grid data and sets the textData
state, updating the textArea:
zinggrid-svelte-demo/src/demos/TwoWay.svelte
const dataChange = (ev) => {
textData = JSON.stringify(ev.target.data)
}
By using two different component variables, one for the grid and one for the textarea, we avoid a loop where editing the grid triggers a change in the textarea, when then triggers a change to the grid, which triggers another update to the textarea, and so on.
Conditional Rendering (zinggrid-svelte-demo/src/demos/Conditional.svelte
)
In this demo we see how a ZingGrid can be used to show multiple datasets. A Svelte component variable defaultColumns
is used in multiple places in the grid definition to select between two different datasets and sets of column definitions:
zinggrid-svelte-demo/src/demos/Conditional.svelte
<div class="Grid-wrapper">
<button on:click={() => defaultColumns = !defaultColumns}>
Switch to
{defaultColumns ? " Second " : " First "}
Dataset
</button>
<zing-grid
data={defaultColumns ? data1 : data2}
caption={defaultColumns ? "Shows 1" : "Shows 2"}
editor
loading
layout="row"
head-class="grid-header"
viewport-stop
pager={true}
page-size="5"
>
{#if defaultColumns}
<zg-colgroup>
<zg-column index="title" />
<zg-column index="genre" />
</zg-colgroup>
{:else}
<zg-colgroup>
<zg-column index="titleChanged" />
<zg-column index="genreChanged" />
</zg-colgroup>
{/if}
</zing-grid>
</div>
A button toggles the value of defaultColumns
, causing the grid to be re-rendered with the alternate data and columns.
Register Method (zinggrid-svelte-demo/src/demos/Register.svelte
)
This final demo focuses on registering a method with ZingGrid to be used to change how data is rendered in the grid. We touched on this briefly, above, in the GraphQL/CRUD demo, where we used a render method to filter out possible cross-site scripting attacks.
By specifying the renderer
attribute in a zg-column
definition:
zinggrid-svelte-demo/src/demos/Register.svelte
<zing-grid
bind:this={grid}
head-class="grid-header"
caption="Hello World">
<zg-colgroup>
<zg-column index="first" header="Column 1" renderer="customRenderer"></zg-column>
<zg-column index="last" header="Column 2"></zg-column>
<zg-column index="number" header="Column 3"></zg-column>
</zg-colgroup>
</zing-grid>
we can change how the data in that column is displayed to the user. The data itself is not modified, but each value is passed as the argument to the renderer
function, and the function's return value is what is shown to the user.
In order to make the renderer function available to ZingGrid, we register it with registerMethod
. The first argument is the function itself, the second argument is the name of the function as it appears in the renderer
attribute in the grid definition:
zinggrid-svelte-demo/src/demos/Register.svelte
onMount(() => {
grid.setData(dataSet)
ZingGrid.registerMethod(customRenderer, 'customRenderer')
})
In this case the name of the function and the name we register it as are the same, but this is not required. The registerMethod
call is executed within onMount
, after the grid has been rendered.
The customRenderer
function is simple, it just converts its input to uppercase:
zinggrid-svelte-demo/src/demos/Register.svelte
const customRenderer = (text) => text.toUpperCase()
but any transformation can be performed.
Conclusion
I hope this article has provided a jumpstart in using ZingGrid in a Svelte application. For more information, please visit the ZingGrid documentation at https://www.zinggrid.com/docs.
Interested in learning more about ZingGrid or trying it out for yourself? Let us know at support@zinggrid.com or visit us at https://www.zinggrid.com and chat directly with our team!