@teipublisher/pb-components
Version:
Collection of webcomponents underlying TEI Publisher
273 lines (255 loc) • 7.53 kB
JavaScript
import { LitElement, html, css } from 'lit-element';
import { Grid } from 'gridjs';
import { pbMixin, waitOnce } from './pb-mixin.js';
import { resolveURL } from './utils.js';
import { loadStylesheets, importStyles } from './theming.js';
import '@polymer/paper-input/paper-input';
import '@polymer/iron-icons';
import '@polymer/iron-form';
import '@polymer/paper-icon-button';
import './pb-table-column.js';
import { registry } from './urls.js';
/**
* A table grid based on [gridjs](https://gridjs.io/), which loads its data from a server endpoint
* specified in `source`. If `source` is a relative URI, it will be resolved relative to the
* TEI Publisher endpoint.
*
* The JSON data returned by the endpoint should be an object with two properties:
*
* * `count`: the overall number of rows available on the server
* * `results`: an array containing each record as an object
*
* The parameters send to the server are as follows:
*
*
* Parameter | Description
* ---------|----------
* limit | number of records to return for each page
* start | start offset from which to return records
* order | the id of the column to sort by
* dir | sort direction: either 'asc' or 'desc'
* search | an optional search string entered by the user
*
* Table columns are configured via nested `<pb-table-column>` elements:
*
* ```html
* <pb-table-column label="Name" property="name" sort width="33%"></pb-table-column>
* <pb-table-column label="Born" property="birth"></pb-table-column>
* <pb-table-column label="Died" property="death"></pb-table-column>
* ```
*/
export class PbTableGrid extends pbMixin(LitElement) {
static get properties() {
return {
/**
* URI of the server-side endpoint to retrieve data from.
* Relative URIs are resolved relative to the configured TEI Publisher endpoint.
*/
source: {
type: String,
},
/**
* Path to the gridjs theme CSS files.
*/
cssPath: {
type: String,
attribute: 'css-path',
},
/**
* If specified, columns (without a fixed width) will be resizable.
*/
resizable: {
type: Boolean,
},
subforms: {
type: String,
},
perPage: {
type: Number,
attribute: 'per-page',
},
height: {
type: String,
},
/**
* If specified, enable server-side search.
*/
search: {
type: Boolean,
},
_params: {
type: Object,
},
...super.properties,
};
}
constructor() {
super();
this.cssPath = '../css/gridjs';
this._params = {};
this.resizable = false;
this.search = false;
this.perPage = 10;
this.height = null;
this.fixedHeader = false;
}
async connectedCallback() {
super.connectedCallback();
this.subscribeTo('pb-search-resubmit', ev => {
this._submit();
});
registry.subscribe(this, state => {
this._params = state;
this._submit();
});
this.subscribeTo(
'pb-i18n-update',
ev => {
const needsRefresh = this.language && this.language !== ev.detail.language;
this.language = ev.detail.language;
if (needsRefresh) {
this._submit();
}
},
[],
);
if (!this.height) {
const property = getComputedStyle(this).getPropertyValue('--pb-table-grid-height');
if (property) {
this.height = property;
} else {
this.height = 'auto';
}
}
const gridjsTheme = await loadStylesheets([`${resolveURL(this.cssPath)}/mermaid.min.css`]);
const theme = importStyles(this);
const sheets = [...this.shadowRoot.adoptedStyleSheets, gridjsTheme];
if (theme) {
sheets.push(theme);
}
this.shadowRoot.adoptedStyleSheets = sheets;
}
firstUpdated() {
const table = this.shadowRoot.getElementById('table');
const pbColumns = this.querySelectorAll('pb-table-column');
const columns = [];
pbColumns.forEach(column => columns.push(column.data()));
waitOnce('pb-page-ready', data => {
if (data && data.language) {
this.language = data.language;
}
this._params = registry.state;
const url = this.toAbsoluteURL(this.source);
const config = {
height: this.height,
fixedHeader: true,
columns,
resizable: this.resizable,
server: {
url,
then: data => data.results,
total: data => data.count,
},
sort: {
multiColumn: false,
enabled: true,
server: {
url: (prev, cols) => {
if (!cols.length) return prev;
const col = cols[0];
return `${prev}${prev.indexOf('?') > -1 ? '&' : '?'}order=${
columns[col.index].id
}&dir=${col.direction === 1 ? 'asc' : 'desc'}`;
},
},
},
pagination: {
enabled: true,
limit: this.perPage,
server: {
url: (prev, page, limit) => {
const form = this.shadowRoot.getElementById('form');
if (form) {
Object.assign(this._params, form.serializeForm());
}
this._params = this._paramsFromSubforms(this._params);
this._params.limit = limit;
this._params.start = page * limit + 1;
if (this.language) {
this._params.language = this.language;
}
registry.commit(this, this._params);
// copy params and remove null values
const urlParams = { ...this._params };
Object.keys(urlParams).forEach(key => {
if (urlParams[key] === null) {
delete urlParams[key];
}
});
return `${prev}${prev.indexOf('?') > -1 ? '&' : '?'}${new URLSearchParams(
urlParams,
).toString()}`;
},
},
},
};
this.grid = new Grid(config);
this.grid.on('load', () => {
this.emitTo('pb-results-received', {
params: this._params,
});
});
this.grid.render(table);
});
}
_submit() {
this.grid.forceRender();
}
_paramsFromSubforms(params) {
if (this.subforms) {
document.querySelectorAll(this.subforms).forEach(form => {
if (form.serializeForm) {
Object.assign(params, form.serializeForm());
}
});
}
return params;
}
render() {
return html`
${this.search
? html`
<iron-form id="form">
<form action="">
<paper-input
id="search"
name="search"
label="Search"
value="${this._params.search || ''}"
="${e => (e.keyCode == 13 ? this._submit() : null)}"
>
<paper-icon-button
icon="search"
="${this._submit}"
slot="suffix"
></paper-icon-button>
</paper-input>
</form>
</iron-form>
`
: null}
<div id="table"></div>
`;
}
static get styles() {
return css`
:host {
display: block;
}
button {
border: 0;
}
`;
}
}
customElements.define('pb-table-grid', PbTableGrid);