Skip to content

Configure views#

This page covers the settings and methods shared by every Storyteller view: Story and Clips rows and grids, and the two Clips player views. For the settings that only rows or only grids use, see Add a Story or Clips row and Add a Story or Clips grid.

Note

Most examples use StorytellerStoriesRowView. The same settings apply to StorytellerStoriesGridView, StorytellerClipsRowView, and StorytellerClipsGridView. Where StorytellerClipsPlayerView or StorytellerEmbeddedClipsPlayerView works differently, the section says so.

Initialization#

Create each view with the ID of an existing element on your page, followed by its content source.

Stories initialization#

const storyRow = new Storyteller.StorytellerStoriesRowView('stories-row-id'); // All Categories
const storyRowWithCategories = new Storyteller.StorytellerStoriesRowView(
  'stories-row-id',
  ['category-1', 'category-2']
);

Clips initialization#

const clipsRow = new Storyteller.StorytellerClipsRowView(
  'clips-row-id',
  'clip-collection-id'
);

Clips player initialization#

StorytellerClipsPlayerView shows the Clips player in your container instead of opening it from a row or grid. Pass a collection ID to play the collection, or { clipId } or { externalId } to play one Clip. For a player inside other page content, such as a live blog, use StorytellerEmbeddedClipsPlayerView. Add a Clips player to a page compares the two views.

const collectionPlayer = new Storyteller.StorytellerClipsPlayerView(
  'clips-player-id',
  'clip-collection-id'
);

const singleClipPlayer = new Storyteller.StorytellerClipsPlayerView(
  'clips-player-id',
  { clipId: 'clip-id' }
);

const singleClipPlayerByExternalId = new Storyteller.StorytellerClipsPlayerView(
  'clips-player-id',
  { externalId: 'clip-external-id' }
);

The constructor throws an Error if you pass no source or more than one source.

The Storyteller Web Showcase picks a Stories row or grid for each feed module from its layout with isGridLikeLayout.

Story ordering#

Rows and grids can order Stories by what the user has already read. Storyteller sets the ordering for your content in the Stories API response; you don't set it in code. With the default read ordering, unread Stories come before read Stories. With started-aware ordering, Stories appear in three groups: not opened, started, and finished. Pinned Stories stay first, and Live Stories stay ahead of the other Stories. Within each group, Stories keep the order set in the Storyteller CMS.

Clips paging#

Clips rows, grids, and players that show a collection load more Clips when the user reaches the last loaded Clip, including inside a Clip Category. The SDK skips Clips it has already shown and stops when the collection has no more Clips. You don't need to change your code.

  • reloadData loads the first page again.
  • The view's onDataLoadComplete callback reports the first page only. Later pages load without delegate callbacks.
  • A Clips player that shows one Clip, created with { clipId } or { externalId }, doesn't load more Clips.

Clip details#

The Clips player shows a Clip's long description when the Clip has one. The title, description, and Categories show a short preview, and one control expands or collapses them. Long details scroll inside the player. A Clip without a description shows its title and Categories. To hide the title, see the Clips player theme settings.

Right-to-left (RTL) pages#

The Web SDK supports right-to-left host pages for Stories rows, Clips rows, and the Story player.

Host page setup#

Set dir="rtl" on the page or on the closest container that wraps the SDK view:

<section dir="rtl">
  <div id="stories-row-id"></div>
</section>
const storyRow = new Storyteller.StorytellerStoriesRowView('stories-row-id');

The SDK reads the text direction from the view's container and its ancestors, so one page can mix directions. For example, a left-to-right page can show one Storyteller row inside a right-to-left section.

Supported surfaces#

RTL support applies to:

  • StorytellerStoriesRowView
  • StorytellerClipsRowView
  • Story players opened from Stories rows and grids

In a right-to-left row, the scroll controls, edge fades, and disabled states follow the right-to-left scroll direction.

When a Story opens from an RTL page, the Story player uses right-to-left text and controls.

Configuration#

Set a view's options by assigning an object to its configuration property. Assigning a partial object updates only the fields it contains. In TypeScript, type the object with IListConfiguration<'StorytellerStoriesRowView'> (use the view's class name), IStorytellerClipsPlayerConfiguration, or IStorytellerEmbeddedClipsPlayerConfiguration.

TypeScript examples

The TypeScript examples in these guides assume import * as Storyteller from '@getstoryteller/storyteller-sdk-javascript'; and import the configuration types they use, for example import type { IListConfiguration } from '@getstoryteller/storyteller-sdk-javascript';.

Stories configuration#

const customTheme = new Storyteller.UiTheme(); // See Customize themes
const storyRow = new Storyteller.StorytellerStoriesRowView('stories-row-id');
storyRow.configuration = {
  basename: 'top-stories', // Needed if there are multiple lists on the same page
  categories: ['category1', 'category2', 'category3'], // Stories only
  cellType: Storyteller.CellType.round, // StorytellerStoriesRowView only
  context: {
    source: 'homepage-stories',
    campaign: 'summer-league'
  },
  displayLimit: 10,
  preload: true, // Stories only
  theme: customTheme,
  uiStyle: Storyteller.UiStyle.dark,
};
const customTheme = new Storyteller.UiTheme(); // See Customize themes
const storyRow = new Storyteller.StorytellerStoriesRowView('stories-row-id');
const storyRowConfiguration: IListConfiguration<'StorytellerStoriesRowView'> = {
  basename: 'top-stories', // Needed if there are multiple lists on the same page
  categories: ['category1', 'category2', 'category3'], // Stories only
  cellType: Storyteller.CellType.round, // StorytellerStoriesRowView only
  context: {
    source: 'homepage-stories',
    campaign: 'summer-league'
  },
  displayLimit: 10,
  preload: true, // Stories only
  theme: customTheme,
  uiStyle: Storyteller.UiStyle.dark,
};
storyRow.configuration = storyRowConfiguration;

Clips configuration#

const customTheme = new Storyteller.UiTheme(); // See Customize themes
const clipsRow = new Storyteller.StorytellerClipsRowView(
  'clips-row-id',
  'collection-id'
);
clipsRow.configuration = {
  basename: 'top-clips', // Needed if there are multiple lists with the same collection on the same page
  context: {
    source: 'homepage-clips',
    campaign: 'summer-league'
  },
  displayLimit: 10,
  theme: customTheme,
  uiStyle: Storyteller.UiStyle.dark,
};
const customTheme = new Storyteller.UiTheme(); // See Customize themes
const clipsRow = new Storyteller.StorytellerClipsRowView(
  'clips-row-id',
  'collection-id'
);
const clipsRowConfiguration: IListConfiguration<'StorytellerClipsRowView'> = {
  basename: 'top-clips', // Needed if there are multiple lists with the same collection on the same page
  context: {
    source: 'homepage-clips',
    campaign: 'summer-league'
  },
  displayLimit: 10,
  theme: customTheme,
  uiStyle: Storyteller.UiStyle.dark,
};
clipsRow.configuration = clipsRowConfiguration;

Clips player configuration#

const customTheme = new Storyteller.UiTheme(); // See Customize themes
const clipPlayer = new Storyteller.StorytellerClipsPlayerView(
  'clips-player-id',
  { externalId: 'clip-external-id' }
);
clipPlayer.configuration = {
  basename: 'highlight-player',
  context: {
    source: 'homepage-highlight',
    campaign: 'google-highlights'
  },
  theme: customTheme,
  uiStyle: Storyteller.UiStyle.dark,
};
clipPlayer.topLevelBackButtonEnabled = true;
clipPlayer.delegate = {
  onTopLevelBackTapped: () => {
    window.history.back();
  },
};
const customTheme = new Storyteller.UiTheme(); // See Customize themes
const clipPlayer = new Storyteller.StorytellerClipsPlayerView(
  'clips-player-id',
  { externalId: 'clip-external-id' }
);
const clipPlayerConfiguration: IStorytellerClipsPlayerConfiguration = {
  basename: 'highlight-player',
  context: {
    source: 'homepage-highlight',
    campaign: 'google-highlights'
  },
  theme: customTheme,
  uiStyle: Storyteller.UiStyle.dark,
};
clipPlayer.configuration = clipPlayerConfiguration;
clipPlayer.topLevelBackButtonEnabled = true;
clipPlayer.delegate = {
  onTopLevelBackTapped: () => {
    window.history.back();
  },
};

Embedded Clips player configuration#

StorytellerEmbeddedClipsPlayerView uses the same configuration fields as StorytellerClipsPlayerView:

const customTheme = new Storyteller.UiTheme(); // See Customize themes
const embeddedClipPlayer =
  new Storyteller.StorytellerEmbeddedClipsPlayerView(
    'embedded-clips-player-id',
    { externalId: 'clip-external-id' }
  );
embeddedClipPlayer.configuration = {
  basename: 'live-blog-highlight-player',
  context: {
    location: 'live-blog',
    module: 'highlight-player',
  },
  theme: customTheme,
  uiStyle: Storyteller.UiStyle.dark,
};
embeddedClipPlayer.topLevelBackButtonEnabled = true;
embeddedClipPlayer.delegate = {
  onTopLevelBackTapped: () => {
    window.history.back();
  },
};
const customTheme = new Storyteller.UiTheme(); // See Customize themes
const embeddedClipPlayer =
  new Storyteller.StorytellerEmbeddedClipsPlayerView(
    'embedded-clips-player-id',
    { externalId: 'clip-external-id' }
  );
const embeddedClipPlayerConfiguration: IStorytellerEmbeddedClipsPlayerConfiguration = {
  basename: 'live-blog-highlight-player',
  context: {
    location: 'live-blog',
    module: 'highlight-player',
  },
  theme: customTheme,
  uiStyle: Storyteller.UiStyle.dark,
};
embeddedClipPlayer.configuration = embeddedClipPlayerConfiguration;
embeddedClipPlayer.topLevelBackButtonEnabled = true;
embeddedClipPlayer.delegate = {
  onTopLevelBackTapped: () => {
    window.history.back();
  },
};

Learn more

See Add a Story or Clips row and Add a Story or Clips grid for the settings specific to rows and grids.

basename#

Each Stories or Clips view has a basename, which is the first segment of the player's hash URL. For example:

https://www.getstoryteller.com/#basename/story-id
https://www.getstoryteller.com/#basename/collection-id/clip-id

By default, the basename is stories for Stories views and clips for Clips views. To change it, set basename in configuration:

storyRow.configuration = {
  basename: 'top-stories',
};

If a page has only one Stories or Clips view, basename is optional. Opening a Story then goes to #stories/story-id, and opening a Clip goes to #clips/collection-id/clip-id.

If a page has several views, the SDK derives each view's basename from its Categories or collection. If two views have the same Categories or collection, set a different basename on each so that every view has a unique basename. Views with different Categories don't need one, but you can set it to make the player URL easier to read.

Note

The SDK removes every character from a basename except ASCII letters, numbers, dashes (-), and underscores (_).

categories (Stories only)#

Set categories to show only Stories from those Categories. Without categories, the view shows the Stories in your Home list.

To change the Categories of a Storyteller row, assign new ones in configuration:

storyRow.configuration = {
  categories: ['new-category-id-1', 'new-category-id-2'],
};

Copy Category IDs from the Storyteller CMS.

Learn more

See Categories for more information about managing Categories in the CMS.

collection (Clips only)#

A Clips view needs a collection ID when you create it:

const clipsRow = new Storyteller.StorytellerClipsRowView(
  'clips-row-id',
  'clip-collection-id'
);

To change the collection later, set collection in configuration:

clipsRow.configuration = {
  collection: 'new-clip-collection-id',
};

Copy the collection ID from the Storyteller CMS.

Learn more

See Creating Collections for more information about managing collections in the CMS.

clipId / externalId (Clips player only)#

To play a single Clip in StorytellerClipsPlayerView or StorytellerEmbeddedClipsPlayerView, pass an object with either clipId or externalId:

const clipPlayerById = new Storyteller.StorytellerClipsPlayerView(
  'clips-player-id',
  { clipId: 'clip-id' }
);

const clipPlayerByExternalId = new Storyteller.StorytellerClipsPlayerView(
  'clips-player-id',
  { externalId: 'clip-external-id' }
);

const embeddedClipPlayerByExternalId =
  new Storyteller.StorytellerEmbeddedClipsPlayerView(
    'embedded-clips-player-id',
    { externalId: 'clip-external-id' }
  );

To switch the player to a different Clip later, assign the new identifier in configuration:

clipPlayerById.configuration = {
  clipId: 'new-clip-id',
};

clipPlayerByExternalId.configuration = {
  externalId: 'new-clip-external-id',
};

Set exactly one source on a Clips player: collection, clipId, or externalId. The constructor throws an Error if you pass no source or more than one. A later configuration update with an invalid source logs an error and keeps the current source.

The IStorytellerClipsPlayerConfiguration and IStorytellerEmbeddedClipsPlayerConfiguration types include the same source fields.

topLevelBackButtonEnabled (Clips player only)#

Set topLevelBackButtonEnabled on the player itself, not in configuration. It shows a back button at the top of a StorytellerClipsPlayerView or StorytellerEmbeddedClipsPlayerView:

const clipPlayer = new Storyteller.StorytellerClipsPlayerView(
  'clips-player-id',
  { externalId: 'clip-external-id' }
);

clipPlayer.topLevelBackButtonEnabled = true;
clipPlayer.delegate = {
  onTopLevelBackTapped: () => {
    window.history.back();
  },
};

When the user taps the back button:

  • If clipPlayer.delegate.onTopLevelBackTapped is defined, the SDK calls it and your code decides what happens next.
  • If no callback is defined, the SDK calls window.history.back().

topLevelBackButtonEnabled defaults to false, which hides the button. While the button is hidden, the SDK doesn't call onTopLevelBackTapped.

context#

Use context to identify the placement that produced an analytics event. The SDK returns its value in UserActivityData.context for each onUserActivityOccurred callback from the view.

clipsRow.configuration = {
  context: {
    location: 'home',
    module: 'featured-clips',
    sortOrder: 20,
  },
};

The field accepts any value. An undefined value omits context from the callback. Explicit values such as null, false, 0, and an empty string stay in the callback. Reassign configuration to replace the value while the view is on the page.

When a user opens related Story or Clip content through an SDK action, the related player keeps the source view's context. If two matching placements share a player, events use the context from the placement that the user chose. The configured value returns after the player is dismissed.

The SDK adds context to the browser callback only. It doesn't add the field to Storyteller analytics API requests. The callback runs only while enableUserActivityTracking is on. Don't put credentials or personal data in context. See Analytics for the supported views, deep-link behavior, tracking controls, and data handling guidance.

displayLimit#

displayLimit is the maximum number of tiles the view shows. It's optional.

preload#

Each Stories row or grid starts a low-priority download of the AMP player script before the user opens a Story. Set preload to true to also prepare the Story player in advance. The first Story then opens sooner if that work finishes in time, but users who never open a Story download more code. preload defaults to false. Keep the default when the first page load matters most.

theme#

Set theme to a UiTheme to style one view. It overrides the global theme (Storyteller.sharedInstance.theme) for that view only: the properties you set replace the global values, and the rest come from the global theme. A theme set in configuration stays in place when initialize runs again. See Customize themes.

const customTheme = new Storyteller.UiTheme({
  light: {
    lists: {
      row: {
        startInset: 0,
        endInset: 0,
      },
    },
  },
});

storyRow.configuration = {
  theme: customTheme,
};

uiStyle#

uiStyle sets whether the view uses the light theme, the dark theme, or follows the system setting. It takes a Storyteller.UiStyle value. JavaScript can also pass the strings 'auto', 'light', and 'dark'.

  • Storyteller.UiStyle.auto (default): the view follows the system light or dark mode
  • Storyteller.UiStyle.light: the view always uses the light theme
  • Storyteller.UiStyle.dark: the view always uses the dark theme

You can also set uiStyle on the container div with the data-ui-style attribute:

<div id="storyteller-row" data-ui-style="dark"></div>

Methods#

reloadData#

reloadData(): Promise<void>

reloadData loads the view's Stories or Clips from the API again. When the request finishes, the view updates its tiles, starts prefetching content, and updates the read status of its Stories or Clips. The view calls onDataLoadStarted and then onDataLoadComplete on its delegate, with the result of the request. Clips views load the first page again. The promise resolves when loading finishes.

const storyRow = new Storyteller.StorytellerStoriesRowView('stories-row-id');
storyRow.reloadData();

destroy#

destroy(): void

Call destroy before you remove or replace the view's container, for example when a single-page application changes route. The view removes what it rendered, removes its player container when no other view uses it, stops loading content and calling its delegate, and stops following theme changes. Calling destroy again does nothing. To show content in the same element again, create a new view. destroy is available on every view from version 11.0.0.

storyRow.destroy();

For React and Next.js, see Use React or Next.js.

openStory, openStoryByExternalId, openPage, openCollection, openClipByExternalId#

These methods, and openCategory, are on Storyteller.sharedInstance, not on views.

Learn more

See Open a player programmatically for examples, and Use additional SDK methods for the full signatures.