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.

intermediate7 min1 step

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.

Prompt Recipe

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.

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