Row Odd
Apply configurable row striping for better readability and data distinction with the help of the RowOddPlugin.
Source code
import { defineCustomElements } from '@revolist/revogrid/loader';import { RowOddPlugin } from '@revolist/revogrid-pro';import { currentTheme } from '../composables/useRandomData';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();const { isDark } = currentTheme();
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 = isDark() ? 'darkMaterial' : '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();}<template> <div class="row-odd-demo"> <row-odd-demo-toolbar ref="toolbar" @row-odd-toolbar-action="handleToolbarAction" @row-odd-toolbar-options-change="setToolbarOptions" /> <RevoGrid :class="{ 'cell-border': bordered }" :columns="columns" :plugins="plugins" :row-odd.prop="rowOdd" :source="source" :theme="isDark ? 'darkMaterial' : 'material'" hide-attribution /> </div></template>
<script setup lang="ts">import RevoGrid from '@revolist/vue3-datagrid';import { RowOddPlugin } from '@revolist/revogrid-pro';import { currentThemeVue } from '../composables/useRandomData';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 { isDark } = currentThemeVue();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 rowOdd = computed(() => 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>import React, { useEffect, useMemo, useRef, useState } from 'react';import { RevoGrid } from '@revolist/react-datagrid';import { RowOddPlugin } from '@revolist/revogrid-pro';import { currentTheme } from '../composables/useRandomData';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();const { isDark } = currentTheme();
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 rowOdd = useMemo( () => 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 className={bordered ? 'cell-border' : ''} columns={columns} hideAttribution plugins={plugins} rowOdd={rowOdd} source={source} theme={isDark() ? 'darkMaterial' : 'material'} /> </div> );}
export default RowOdd;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 { currentTheme } from '../composables/useRandomData';import { createRowOddConfig, createRowOddRows, initialRowOddState, rowOddColumns, type RowOddDemoMode,} from './row-odd-shared';import { defineRowOddToolbarElement, type RowOddToolbarActionEvent, type RowOddToolbarElement, type RowOddToolbarOptionsChangeEvent,} from './row-odd-toolbar';
defineRowOddToolbarElement();const { isDark } = currentTheme();
@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]="theme" ></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]; theme = isDark() ? 'darkMaterial' : 'material'; 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, }; } }}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, };}import { createDropdownControl, type DropdownControl,} from '@revolist/ui/dropdown/control';import { applyControlPresentation } from '@revolist/ui';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 dropdown?: DropdownControl; disconnectedCallback() { this.dropdown?.destroy(); } 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.dropdown?.destroy(); this.innerHTML = ` <div class="row-odd-toolbar" role="group" aria-label="Row striping options"> <div data-mode></div> <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> `; applyControlPresentation(this);
this.dropdown = createDropdownControl(this.ownerDocument, { source: [ { value: 'odd', label: 'odd' }, { value: 'even', label: 'even' }, { value: 'custom', label: 'custom' }, { value: 'priority', label: 'High priority' }, ], value: mode, config: { ariaLabel: 'Stripe mode' }, onChange: (value) => this.emitOptionsChange({ mode: String(value) as RowOddDemoMode }), }); this.querySelector('[data-mode]')?.append(this.dropdown.element); 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 renderToggle(label: string, key: 'bordered', pressed: boolean) { return ` <button class="rv-btn" type="button" data-toggle="${key}" aria-pressed="${pressed}"> ${label} </button> `; }}.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; }}
html[data-theme='dark'] .row-odd-demo revo-grid,html.dark .row-odd-demo revo-grid,.row-odd-demo 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;
> [data-mode] { flex: 0 0 140px; min-width: 0; } > label { flex: 0 0 auto; white-space: nowrap; margin: 0; } > button { flex: 0 0 auto; }
label { align-items: center; display: inline-flex; font-size: 13px; gap: 6px; }
input[type='number'] { flex: 0 0 72px; 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: 500;}import { RowOddPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowOddPlugin];grid.rowOdd = { mode: 'custom', interval: 3, offset: 1, className: 'priority-band',};