Server-side grid with Open Brewery DB

author(s): OpenAI Codex

by stitot

HTML

<script src="https://cdn.jsdelivr.net/npm/@highcharts/grid-pro/grid-pro.js"></script>

<div class="demo-shell">
    <div class="demo-intro">
        <h1>Grid Pro with server-side breweries</h1>
        <p>
            This demo uses the public Open Brewery DB API, so sorting,
            filtering, and pagination all happen on the server. Your browser
            gets a small page of rows instead of the whole brewery universe at
            once. Your laptop fan may now stand down.
        </p>
    </div>

    <div id="notice" class="notice" aria-live="polite">
        Loading rows from Open Brewery DB...
    </div>

    <div id="container"></div>

    <p class="highcharts-description">
        Try clicking the <strong>Brewery</strong>, <strong>City</strong>, or
        <strong>State</strong> headers to sort. Use the inline filters to fetch
        filtered pages directly from the API.
    </p>
</div>

CSS

@import url("https://cdn.jsdelivr.net/npm/@highcharts/grid-pro/css/grid-pro.css");

body {
    font-family:
        -apple-system,
        BlinkMacSystemFont,
        "Segoe UI",
        Roboto,
        Helvetica,
        Arial,
        sans-serif;
    background:
        linear-gradient(180deg, #f8fafc 0%, #eef4ea 100%);
    color: #1f2937;
    margin: 0;
}

.demo-shell {
    max-width: 1100px;
    margin: 0 auto;
    padding: 20px 16px 28px;
}

.demo-intro {
    margin-bottom: 14px;
}

.demo-intro h1 {
    margin: 0 0 8px;
    font-size: 1.8rem;
    line-height: 1.1;
}

.demo-intro p,
.highcharts-description {
    margin: 0;
    max-width: 78ch;
    line-height: 1.55;
}

.notice {
    margin: 0 0 12px;
    padding: 10px 12px;
    border: 1px solid #d6e5d2;
    border-radius: 10px;
    background: #f4fbf2;
    color: #305b2f;
}

.notice[data-level="warning"] {
    border-color: #f2d39b;
    background: #fff8ec;
    color: #7a4d09;
}

.notice[data-level="error"] {
    border-color: #f0b4b4;
    background: #fff1f1;
    color: #8b1e1e;
}

#container {
    height: 600px;
}

.highcharts-description {
    padding-top: 12px;
}

JavaScript

const API_BASE = 'https://api.openbrewerydb.org/v1/breweries';
const FILTER_PARAM_MAP = {
    name: 'by_name',
    city: 'by_city',
    state: 'by_state',
    brewery_type: 'by_type'
};
const SORTABLE_COLUMNS = new Set(['name', 'city', 'state', 'brewery_type']);
const SUPPORTED_FILTER_OPERATORS = new Set([
    'contains',
    'startsWith',
    'endsWith',
    '==='
]);

const noticeEl = document.getElementById('notice');

function setNotice(message, level) {
    noticeEl.textContent = message;
    noticeEl.dataset.level = level || 'info';
}

function getActiveSortings(query) {
    const sortings = query.sorting.currentSortings ||
        (query.sorting.currentSorting ? [query.sorting.currentSorting] : []);

    return sortings.filter((sorting) => sorting?.columnId && sorting.order);
}

function extractFilterConditions(condition, negated, result) {
    if (!condition) {
        return result;
    }

    if (condition.operator === 'and' || condition.operator === 'or') {
        (condition.conditions || []).forEach((subCondition) => {
            extractFilterConditions(subCondition, negated, result);
        });

        return result;
    }

    if (condition.operator === 'not') {
        extractFilterConditions(condition.condition, true, result);
        return result;
    }

    if (condition.columnId) {
        result.push({
            columnId: condition.columnId,
            operator: condition.operator,
            value: condition.value,
            negated
        });
    }

    return result;
}

function getActiveFilters(query) {
    const rootCondition = query.filtering.modifier?.options?.condition;

    return extractFilterConditions(rootCondition, false, []);
}

function normalizeFilterValue(value) {
    return String(value || '').trim();
}

function buildApiParams(query, offset, limit) {
    const page = Math.floor(offset / limit) + 1;
    const listParams = new...