Skip to main content

Gate a tour step on what the screen actually renders

Free Intermediate

A tour step waits for its data-tour target and then gives up. If the element is one the screen only sometimes draws, give the step a condition, and a step that cannot work is dropped before the tour starts.

The failure a condition prevents is silent. The tour opens the step’s screen and waits up to two seconds for the target. When it never appears nothing errors and nothing is logged: the tip is shown floating over the page, attached to nothing. You only find out by running the tour on a site where the element is missing.

  • When it runs. Once, when the tour launches, before the step’s screen is worked out.
    false removes the step entirely: the step counter is one shorter and nothing is greyed out. A tour whose every step is removed does not start.
  • It receives the show and may be async. The signature is
    condition?: ( showId: number | null ) => boolean | Promise<boolean>. A plain () => boolean is valid and is the usual case. Return a promise when only the server knows the answer, as in the second step of the example.
  • Answer false on every failure — a timeout, an error response, an unexpected shape,
    no show selected. A dropped step is invisible; a kept step whose target never renders is the pause and the tip attached to nothing.
  • Ask; do not read a cached copy of the answer. A tour can be launched from the Tours
    catalog on any screen and from the setup wizard, so the data you expect to be loaded often is not, and a cold cache reads as “no”.
  • Share the predicate with whatever renders the element. If your component decides
    whether to draw the target with a function, call the same function here. Two copies of “is it on screen?” drift apart.
  • A condition is not a dynamic screen. screen: 'episode-references' asks whether there
    is anywhere to go; a condition asks whether the step is wanted. A step can need both.
  • The tour-level condition is different and must stay synchronous. It decides whether
    the whole tour is offered and is called without awaiting, so an async one returns a promise, which is truthy, and the tour is always offered.

Three values on window.benecasterAdmin answer the questions conditions usually ask: addons (what the license grants), installedAddons (what is installed), and nativeBridgeActive, which is true when a show has chosen Benecaster’s built-in membership and has at least one active tier. It does not mean Stripe is connected, so it is the right gate for a step about the Memberships screens, aimed at someone still setting up. All three are read when the admin page loads.

Code

window.BenecasterExtensions = window.BenecasterExtensions || {};
window.BenecasterExtensions.tours = window.BenecasterExtensions.tours || [];

window.BenecasterExtensions.tours.push( {
    id:    'my-addon',
    label: 'My Add-on',
    steps: [
        {
            id:      'my-addon-intro',
            target:  '[data-tour="addon-card-benecaster-addon-my-addon"]',
            screen:  'addons',
            title:   'Where your add-on lives',
            content: 'Switch it on per show here.',
        },
        {
            id:      'my-addon-usage',
            target:  '[data-tour="my-addon-usage"]',
            screen:  'addons',
            title:   'Your usage this month',
            content: 'Only shown once there is something to chart.',
            // Only the server knows whether this show has usage to chart.
            condition: async ( showId ) => {
                if ( null === showId || showId <= 0 ) {
                    return false; // No show selected: nothing to ask about.
                }
                try {
                    const res = await fetch(
                        `${ window.benecasterAdmin.apiUrl }shows/${ showId }/my-addon/usage`,
                        { headers: { 'X-WP-Nonce': window.benecasterAdmin.nonce } }
                    );
                    if ( ! res.ok ) {
                        return false;
                    }
                    const data = await res.json();
                    return Array.isArray( data.rows ) && data.rows.length > 0;
                } catch {
                    return false; // When in doubt, drop the step.
                }
            },
        },
    ],
} );

View on GitHub →

Need this built rather than just documented? See our services →