Row Odd
Apply configurable row striping for better readability and data distinction with the help of the RowOddPlugin.
Source code
TypeScript ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import { RowOddPlugin } from '@revolist/revogrid-pro';
import './row-odd-toolbar.scss';
import {
createRowOddConfig,
createRowOddRows,
initialRowOddState,
rowOddColumns,
type RowOddDemoState,
} from './row-odd-shared';
import {
ROW_ODD_TOOLBAR_ACTION_EVENT,
ROW_ODD_TOOLBAR_OPTIONS_CHANGE_EVENT,
createRowOddToolbar,
type RowOddToolbarActionEvent,
type RowOddToolbarOptionsChangeEvent,
} from './row-odd-toolbar';
defineCustomElements();
export function load(parentSelector: string) {
const host = document.querySelector(parentSelector);
if (!host) {
return () => {};
}
const state: RowOddDemoState = { ...initialRowOddState };
const root = document.createElement('div');
root.className = 'row-odd-demo';
const toolbar = createRowOddToolbar(state);
const grid = document.createElement('revo-grid');
const render = () => {
grid.rowOdd = createRowOddConfig(state);
grid.theme = 'material';
grid.classList.toggle('cell-border', state.bordered);
grid.source = createRowOddRows(state.priceShift);
toolbar.state = state;
};
toolbar.addEventListener(ROW_ODD_TOOLBAR_OPTIONS_CHANGE_EVENT, (event: Event) => {
Object.assign(state, (event as RowOddToolbarOptionsChangeEvent).detail);
render();
});
toolbar.addEventListener(ROW_ODD_TOOLBAR_ACTION_EVENT, (event: Event) => {
if ((event as RowOddToolbarActionEvent).detail === 'update-rows') {
state.priceShift += 9;
render();
}
});
grid.columns = rowOddColumns;
grid.plugins = [RowOddPlugin];
grid.hideAttribution = true;
root.append(toolbar, grid);
host.appendChild(root);
render();
return () => root.remove();
}
Vue vue
<template>
<div class="row-odd-demo">
<row-odd-demo-toolbar
ref="toolbar"
@row-odd-toolbar-action="handleToolbarAction"
@row-odd-toolbar-options-change="setToolbarOptions"
/>
<RevoGrid
:additional-data="additionalData"
:class="{ 'cell-border': bordered }"
:columns="columns"
:plugins="plugins"
:source="source"
theme="material"
hide-attribution
/>
</div>
</template>
<script setup lang="ts">
import RevoGrid from '@revolist/vue3-datagrid';
import { RowOddPlugin } from '@revolist/revogrid-pro';
import './row-odd-toolbar.scss';
import { computed, onMounted, ref, watch } from 'vue';
import {
createRowOddConfig,
createRowOddRows,
initialRowOddState,
rowOddColumns,
type RowOddDemoState,
type RowOddDemoMode,
} from './row-odd-shared';
import {
defineRowOddToolbarElement,
type RowOddToolbarActionEvent,
type RowOddToolbarElement,
type RowOddToolbarOptionsChangeEvent,
} from './row-odd-toolbar';
defineRowOddToolbarElement();
const toolbar = ref<RowOddToolbarElement | null>(null);
const mode = ref<RowOddDemoMode>(initialRowOddState.mode);
const interval = ref(initialRowOddState.interval);
const offset = ref(initialRowOddState.offset);
const bordered = ref(initialRowOddState.bordered);
const priceShift = ref(initialRowOddState.priceShift);
const columns = rowOddColumns;
const plugins = [RowOddPlugin];
const source = computed(() => createRowOddRows(priceShift.value));
const toolbarState = computed<RowOddDemoState>(() => ({
mode: mode.value,
interval: interval.value,
offset: offset.value,
bordered: bordered.value,
priceShift: priceShift.value,
}));
const additionalData = computed(() => ({
rowOdd: createRowOddConfig({
mode: mode.value,
interval: interval.value,
offset: offset.value,
}),
}));
function syncToolbar() {
if (toolbar.value) {
toolbar.value.state = toolbarState.value;
}
}
function setToolbarOptions(event: RowOddToolbarOptionsChangeEvent) {
mode.value = event.detail.mode;
interval.value = event.detail.interval;
offset.value = event.detail.offset;
bordered.value = event.detail.bordered;
}
function handleToolbarAction(event: RowOddToolbarActionEvent) {
if (event.detail === 'update-rows') {
priceShift.value += 9;
}
}
onMounted(syncToolbar);
watch(toolbarState, syncToolbar);
</script>
React tsx
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { RowOddPlugin } from '@revolist/revogrid-pro';
import './row-odd-toolbar.scss';
import {
createRowOddConfig,
createRowOddRows,
initialRowOddState,
rowOddColumns,
type RowOddDemoMode,
} from './row-odd-shared';
import {
ROW_ODD_TOOLBAR_ACTION_EVENT,
ROW_ODD_TOOLBAR_OPTIONS_CHANGE_EVENT,
ROW_ODD_TOOLBAR_TAG,
defineRowOddToolbarElement,
type RowOddToolbarActionEvent,
type RowOddToolbarElement,
type RowOddToolbarOptionsChangeEvent,
} from './row-odd-toolbar';
defineRowOddToolbarElement();
function RowOdd() {
const toolbarRef = useRef<RowOddToolbarElement>(null);
const [mode, setMode] = useState<RowOddDemoMode>(initialRowOddState.mode);
const [interval, setIntervalValue] = useState(initialRowOddState.interval);
const [offset, setOffset] = useState(initialRowOddState.offset);
const [bordered, setBordered] = useState(initialRowOddState.bordered);
const [priceShift, setPriceShift] = useState(initialRowOddState.priceShift);
const source = useMemo(() => createRowOddRows(priceShift), [priceShift]);
const columns = useMemo(() => rowOddColumns, []);
const plugins = useMemo(() => [RowOddPlugin], []);
const additionalData = useMemo(
() => ({
rowOdd: createRowOddConfig({ mode, interval, offset }),
}),
[interval, mode, offset],
);
const toolbarState = useMemo(() => ({
mode,
interval,
offset,
bordered,
priceShift,
}), [bordered, interval, mode, offset, priceShift]);
useEffect(() => {
if (toolbarRef.current) {
toolbarRef.current.state = toolbarState;
}
}, [toolbarState]);
useEffect(() => {
const toolbar = toolbarRef.current;
if (!toolbar) {
return;
}
const handleOptionsChange = (event: Event) => {
const next = (event as RowOddToolbarOptionsChangeEvent).detail;
setMode(next.mode);
setIntervalValue(next.interval);
setOffset(next.offset);
setBordered(next.bordered);
};
const handleAction = (event: Event) => {
if ((event as RowOddToolbarActionEvent).detail === 'update-rows') {
setPriceShift(value => value + 9);
}
};
toolbar.addEventListener(ROW_ODD_TOOLBAR_OPTIONS_CHANGE_EVENT, handleOptionsChange);
toolbar.addEventListener(ROW_ODD_TOOLBAR_ACTION_EVENT, handleAction);
return () => {
toolbar.removeEventListener(ROW_ODD_TOOLBAR_OPTIONS_CHANGE_EVENT, handleOptionsChange);
toolbar.removeEventListener(ROW_ODD_TOOLBAR_ACTION_EVENT, handleAction);
};
}, []);
return (
<div className="row-odd-demo">
{React.createElement(ROW_ODD_TOOLBAR_TAG, { ref: toolbarRef })}
<RevoGrid
additionalData={additionalData}
className={bordered ? 'cell-border' : ''}
columns={columns}
hideAttribution
plugins={plugins}
source={source}
theme="material"
/>
</div>
);
}
export default RowOdd;
Angular ts
import {
AfterViewInit,
CUSTOM_ELEMENTS_SCHEMA,
Component,
ElementRef,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import { RowOddPlugin } from '@revolist/revogrid-pro';
import {
createRowOddConfig,
createRowOddRows,
initialRowOddState,
rowOddColumns,
type RowOddDemoMode,
} from './row-odd-shared';
import {
defineRowOddToolbarElement,
type RowOddToolbarActionEvent,
type RowOddToolbarElement,
type RowOddToolbarOptionsChangeEvent,
} from './row-odd-toolbar';
defineRowOddToolbarElement();
@Component({
selector: 'row-odd-grid',
standalone: true,
imports: [RevoGrid],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
encapsulation: ViewEncapsulation.None,
styleUrls: ['./row-odd-toolbar.scss'],
template: `
<div class="row-odd-demo">
<row-odd-demo-toolbar
#toolbarRef
(row-odd-toolbar-action)="handleToolbarAction($event)"
(row-odd-toolbar-options-change)="setToolbarOptions($event)"
></row-odd-demo-toolbar>
<revo-grid
[class.cell-border]="bordered"
[columns]="columns"
[hideAttribution]="true"
[plugins]="plugins"
[rowOdd]="rowOddConfig"
[source]="source"
theme="material"
></revo-grid>
</div>
`,
})
export class RowOddGridComponent implements AfterViewInit {
@ViewChild('toolbarRef', { read: ElementRef })
toolbarRef?: ElementRef<RowOddToolbarElement>;
mode: RowOddDemoMode = initialRowOddState.mode;
interval = initialRowOddState.interval;
offset = initialRowOddState.offset;
bordered = initialRowOddState.bordered;
priceShift = initialRowOddState.priceShift;
source = createRowOddRows(this.priceShift);
columns = rowOddColumns;
plugins = [RowOddPlugin];
rowOddConfig = this.createRowOddConfig();
ngAfterViewInit() {
this.syncToolbar();
}
setToolbarOptions(event: RowOddToolbarOptionsChangeEvent) {
this.mode = event.detail.mode;
this.interval = event.detail.interval;
this.offset = event.detail.offset;
this.bordered = event.detail.bordered;
this.rowOddConfig = this.createRowOddConfig();
this.syncToolbar();
}
handleToolbarAction(event: RowOddToolbarActionEvent) {
if (event.detail === 'update-rows') {
this.priceShift += 9;
this.source = createRowOddRows(this.priceShift);
this.syncToolbar();
}
}
private createRowOddConfig() {
return createRowOddConfig({
mode: this.mode,
interval: this.interval,
offset: this.offset,
});
}
private syncToolbar() {
if (this.toolbarRef) {
this.toolbarRef.nativeElement.state = {
mode: this.mode,
interval: this.interval,
offset: this.offset,
bordered: this.bordered,
priceShift: this.priceShift,
};
}
}
}
Shared ts
import type { ColumnRegular } from '@revolist/revogrid';
export type RowOddMode = 'odd' | 'even' | 'custom';
export interface RowOddConfig<T = RowOddDemoRow> {
enabled?: boolean;
mode?: RowOddMode;
interval?: number;
offset?: number;
className?: string;
match?: Partial<T>;
shouldStripe?: (context: { model: T }) => boolean;
}
export type RowOddDemoMode = RowOddMode | 'priority';
export interface RowOddDemoState {
mode: RowOddDemoMode;
interval: number;
offset: number;
bordered: boolean;
priceShift: number;
}
export interface RowOddDemoRow {
id: number;
product: string;
category: string;
priority: 'high' | 'normal';
price: number;
stock: number;
}
const products = [
['Alpine Jacket', 'Outerwear'],
['Canvas Tote', 'Accessories'],
['Trail Shoes', 'Footwear'],
['Merino Tee', 'Basics'],
['Rain Shell', 'Outerwear'],
['Travel Pack', 'Accessories'],
['City Sneaker', 'Footwear'],
['Thermal Base', 'Basics'],
['Field Vest', 'Outerwear'],
['Laptop Sleeve', 'Accessories'],
] as const;
export const rowOddColumns: ColumnRegular[] = [
{ name: 'ID', prop: 'id', size: 80 },
{ name: 'Product', prop: 'product', size: 190 },
{ name: 'Category', prop: 'category', size: 150 },
{ name: 'Priority', prop: 'priority', size: 120 },
{ name: 'Price', prop: 'price', size: 110 },
{ name: 'Stock', prop: 'stock', size: 100 },
];
export const initialRowOddState: RowOddDemoState = {
mode: 'odd',
interval: 3,
offset: 1,
bordered: false,
priceShift: 0,
};
export function createRowOddRows(priceShift = 0): RowOddDemoRow[] {
return Array.from({ length: 32 }, (_, index) => {
const [product, category] = products[index % products.length];
return {
id: index + 1,
product,
category,
priority: index % 5 === 0 || index % 7 === 0 ? 'high' : 'normal',
price: 80 + ((index * 17 + priceShift) % 140),
stock: 12 + ((index * 11) % 70),
};
});
}
export function createRowOddConfig(state: Pick<RowOddDemoState, 'mode' | 'interval' | 'offset'>): RowOddConfig<RowOddDemoRow> {
if (state.mode === 'priority') {
return {
mode: 'custom',
className: 'row-odd-priority',
match: { priority: 'high' },
};
}
return {
mode: state.mode,
interval: state.mode === 'custom' ? state.interval : undefined,
offset: state.mode === 'custom' ? state.offset : undefined,
className: state.mode === 'custom' ? 'row-odd-custom' : undefined,
};
}
Toolbar ts
import { initialRowOddState, type RowOddDemoMode, type RowOddDemoState } from './row-odd-shared';
export const ROW_ODD_TOOLBAR_TAG = 'row-odd-demo-toolbar';
export const ROW_ODD_TOOLBAR_OPTIONS_CHANGE_EVENT = 'row-odd-toolbar-options-change';
export const ROW_ODD_TOOLBAR_ACTION_EVENT = 'row-odd-toolbar-action';
export type RowOddToolbarAction = 'update-rows';
export type RowOddToolbarOptionsChangeEvent = CustomEvent<RowOddDemoState>;
export type RowOddToolbarActionEvent = CustomEvent<RowOddToolbarAction>;
export function defineRowOddToolbarElement() {
if (customElements.get(ROW_ODD_TOOLBAR_TAG)) {
return;
}
customElements.define(ROW_ODD_TOOLBAR_TAG, RowOddToolbarElement);
}
export function createRowOddToolbar(state: RowOddDemoState) {
defineRowOddToolbarElement();
const toolbar = document.createElement(ROW_ODD_TOOLBAR_TAG) as RowOddToolbarElement;
toolbar.state = state;
return toolbar;
}
export class RowOddToolbarElement extends HTMLElement {
private currentState: RowOddDemoState = { ...initialRowOddState };
set state(state: RowOddDemoState) {
this.currentState = { ...state };
this.render();
}
get state() {
return { ...this.currentState };
}
connectedCallback() {
this.render();
}
private emitOptionsChange(patch: Partial<RowOddDemoState>) {
this.currentState = {
...this.currentState,
...patch,
};
this.dispatchEvent(new CustomEvent<RowOddDemoState>(
ROW_ODD_TOOLBAR_OPTIONS_CHANGE_EVENT,
{ bubbles: true, detail: this.state },
));
this.render();
}
private emitAction(action: RowOddToolbarAction) {
this.dispatchEvent(new CustomEvent<RowOddToolbarAction>(
ROW_ODD_TOOLBAR_ACTION_EVENT,
{ bubbles: true, detail: action },
));
}
private render() {
if (!this.isConnected) {
return;
}
const { mode, interval, offset, bordered } = this.currentState;
this.innerHTML = `
<div class="row-odd-toolbar">
<select data-mode aria-label="Stripe mode">
${this.renderOption('odd', 'odd', mode)}
${this.renderOption('even', 'even', mode)}
${this.renderOption('custom', 'custom', mode)}
${this.renderOption('priority', 'High priority', mode)}
</select>
<label>
<span>Interval</span>
<input data-interval aria-label="Stripe interval" ${mode !== 'custom' ? 'disabled' : ''} max="8" min="1" type="number" value="${interval}" />
</label>
<label>
<span>Offset</span>
<input data-offset aria-label="Stripe offset" ${mode !== 'custom' ? 'disabled' : ''} max="7" min="0" type="number" value="${offset}" />
</label>
${this.renderToggle('Borders', 'bordered', bordered)}
<button class="rv-btn" type="button" data-action="update-rows">Update rows</button>
</div>
`;
this.querySelector<HTMLSelectElement>('[data-mode]')?.addEventListener('change', event => {
this.emitOptionsChange({ mode: (event.currentTarget as HTMLSelectElement).value as RowOddDemoMode });
});
this.querySelector<HTMLInputElement>('[data-interval]')?.addEventListener('input', event => {
this.emitOptionsChange({ interval: Number((event.currentTarget as HTMLInputElement).value) || 2 });
});
this.querySelector<HTMLInputElement>('[data-offset]')?.addEventListener('input', event => {
this.emitOptionsChange({ offset: Number((event.currentTarget as HTMLInputElement).value) || 0 });
});
this.querySelectorAll<HTMLButtonElement>('[data-toggle]').forEach(button => {
button.addEventListener('click', () => {
const key = button.dataset.toggle as 'bordered';
this.emitOptionsChange({ [key]: !this.currentState[key] });
});
});
this.querySelector<HTMLButtonElement>('[data-action="update-rows"]')?.addEventListener('click', () => {
this.emitAction('update-rows');
});
}
private renderOption(value: RowOddDemoMode, label: string, selected: RowOddDemoMode) {
return `<option value="${value}" ${value === selected ? 'selected' : ''}>${label}</option>`;
}
private renderToggle(label: string, key: 'bordered', pressed: boolean) {
return `
<button class="rv-btn" type="button" data-toggle="${key}" aria-pressed="${pressed}">
${label}
</button>
`;
}
}
Styles scss
.row-odd-demo {
display: grid;
gap: 12px;
min-height: 520px;
revo-grid {
min-height: 420px;
--row-odd-background-color: #eef6ff;
--row-stripe-hover-background-color: #dcecff;
}
revo-grid[theme^='dark'] {
--row-odd-background-color: #27384f;
--row-stripe-hover-background-color: #314663;
}
}
.row-odd-toolbar {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 8px;
label {
align-items: center;
display: inline-flex;
font-size: 13px;
gap: 6px;
}
input,
select {
border: 1px solid #c9d2df;
border-radius: 6px;
padding: 6px 8px;
}
input[type='number'] {
width: 72px;
}
}
.row-odd-priority .rgCell:not([auto-merge='child']),
.rgRow[data-row-stripe-class='row-odd-priority'] .rgCell:not([auto-merge='child']) {
font-weight: 600;
}
import { RowOddPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowOddPlugin];grid.rowOdd = { mode: 'custom', interval: 3, offset: 1, className: 'priority-band',};