Drive the dashboard from your page
Apply filters and select marks from your own buttons and dropdowns — the custom controls Tableau’s native UI cannot build.
Why it matters
Reading made your extension a window; this makes it a steering wheel. applyFilterAsync and selectMarksByValueAsync are the primitives behind every custom filter bar, guided-analysis walkthrough, and reset button ever shipped as an extension. The classic move is the scenario button: one click sets five filters across three worksheets — a saved view state the viewer reaches in one gesture instead of ten. The design rule that keeps these honest: your controls change state the viewer could have reached by hand, so the dashboard never lies about what it shows — you just got them there faster.
Write the JavaScript for a Tableau dashboard extension that renders three plain HTML buttons. 'Focus West': on every worksheet that has a Region field, call applyFilterAsync('Region', ['West'], tableau.FilterUpdateType.Replace). 'Highlight Washington': on a worksheet named in a constant, call selectMarksByValueAsync([{ fieldName: 'State', value: ['Washington'] }], tableau.SelectionUpdateType.Replace). 'Reset': clearFilterAsync('Region') on those same worksheets and an empty selectMarksByValueAsync to clear highlighting. Wrap each handler in try/catch — a worksheet without the field throws — and disable each button while its call is in flight.
applyFilterAsync takes the field, the values, and an update type — Replace for set-it-to-this, Add/Remove for nudging an existing selection. It applies per worksheet, which is precisely its power: your one button loops the worksheets and coordinates them, something the native UI needs a spiderweb of filter actions to approximate. selectMarksByValueAsync is its twin for highlighting — field/value pairs in, marks lit up on the viz.
Expect and handle the miss: filtering a field a worksheet does not have throws. The loop below catches per sheet, so one mismatched viz does not kill the whole scenario.
(async () => {
await tableau.extensions.initializeAsync();
const dashboard = tableau.extensions.dashboardContent.dashboard;
// The scenario button: one click, coordinated state everywhere.
async function focusRegion(region) {
for (const ws of dashboard.worksheets) {
try {
await ws.applyFilterAsync(
'Region', [region], tableau.FilterUpdateType.Replace);
} catch (e) {
// No Region field on this sheet — fine, skip it.
}
}
}
async function highlightState(sheetName, state) {
const ws = dashboard.worksheets.find(w => w.name === sheetName);
if (!ws) return;
await ws.selectMarksByValueAsync(
[{ fieldName: 'State', value: [state] }],
tableau.SelectionUpdateType.Replace);
}
async function reset() {
for (const ws of dashboard.worksheets) {
try { await ws.clearFilterAsync('Region'); } catch (e) {}
await ws.selectMarksByValueAsync([], tableau.SelectionUpdateType.Replace);
}
}
document.getElementById('west').onclick = () => focusRegion('West');
document.getElementById('wa').onclick =
() => highlightState('Sale Map', 'Washington');
document.getElementById('reset').onclick = reset;
})();Got what you came for? Mark the stop and the line fills in beneath you.