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...