Just released: Developer preview of Microsoft Teams bot integration for Now Assistant. Try with your Teams ×

Create a YouTube Gallery Card

In the previous guides we have built a Card with an RSS feed and a Card with Liquid layout. Now let's build something more interactive. In this guide we're going to build a Card for a YouTube channel that will show a feature video and a carousel with other videos of the channel. The result should look something like this:

Finished Youtube channel card

If you haven't done that already, you should first get familiar with how to add a Card and open it in the Designer.

A. Adding a Connector

First of all, we need to build a Connector that will fetch the data from YouTube through an API. Since we want to build a connector for a particular YouTube channel, we'll need to get its ID to query the API with. Usually the ID can be found in the url of the channel:

Youtube channel id could be found in the url

If you can't find it there and you are the owner of the YouTube channel you can follow this Google support link to find your channel ID.

Next, let's navigate to Content Manager → Service Connectors and click "Create New Connector". Here we'll give our connector a name and create it as a RSS Feed Connector with the following service url:
https://www.youtube.com/feeds/videos.xml?channel_id=<channel-id>
Since this API returns results in an XML format, we'll check the "Convert XML to JSON" checkbox as well. The final Connector should look like this:

How the Connector should look like

B. Building the Card

Our Card will consist of 4 parts:

  1. A heading that will hold the title of the Card and general Card actions.
  2. A Liquid view that will display the currently selected video.
  3. A carousel that will show other videos available in the channel.
  4. An expandable footer with a link to the Youtube channel that will hide the carousel unless expanded.

The heading

We're going to select the heading element and only change the title of the Card:

Add Card title

The feature video container

This part of the card will be the most interactive. We'll need to create logic to recognize when a different video in the carousel is selected and change the currently played video accordingly. We are going to achieve this by using _ Card actions_.

If you wish to know more about Card actions and Card state please have a look at this article.

First of all, let's get rid of the default Card body that shows the 'IP address' by selecting it and clicking the 'Delete' button on the keyboard. Now, we can add a new Liquid View, which will be the container for our video by clicking or dragging it onto the wire-frame on the right:

Add a new Liquid View

Next, we want to select our newly added Liquid View and set some parameters for it, which will enable us to create a custom Card action. Click the element to see its parameters. We'll set the Listeners value to _all meaning that this Liquid View will be re-rendered every time there is a change and we'll set the modelRoot to be state.model:

Set the parameters for the liquid view

The state.model contains all the data returned from the service, therefore by setting the modelRoot to it we'll allow the API data to be accessed in our custom action.

Create custom Card Action

Now it's time to write the custom Card Action. Let's navigate to the Actions tab and expand the code editor (click on the input field and click 'Esc').

Let's think about the logic of our card. We know that YouTube's API will return a list of videos sorted by date. Initially, we would like the newest video to be shown as the feature and then consequently any other video gets displayed in the carousel. How do we go about that?

What we can do is to create a selectActive function, which will take the API data as a parameter and split all the data coming from the API into:

  1. A selected object which will hold the information about the currently selected video.
  2. A gallery array which will hold the information about the remaining videos.

To split the data we first need to pre-select the first video. Actions contain a function called initialize, which is always run once at the initial render of the Card. Therefore, we'll use it to initialize our currently selected video to the first element from the API and then call the selectActive function (which we'll create later):

action.initialize = function(state) {

  state.model.activeId = "";

  // select first video by default
  if(state.model.items.length>0) {
    state.model.activeId = state.model.items[0].ytvideoId;
  }

  selectActive(state.model);

}

The above snippet card state as a parameter. Since we set the modelRoot to be state.model the state.model now contains the data returned from the API.

Next, let's create the selectActive function:

// split items[] into selected video and gallery items
function selectActive(model) {

  // initialize .selected video and .gallery videos
  model.selected = null;
  model.gallery = { items: [] };

// check if current active video has been set
  if(model.activeId) {
    let i=0;

// only display 5 videos or less at a time
    let cnt = model.items.length > 5 ? 5 : model.items.length;

// loop through the 5 first videos in the response 
    for(i=0;i<=cnt;i++) {

      var video = model.items[i];

      if(video.ytvideoId == model.activeId) {
        // copy the selected video
        model.selected = video;
      } else {
        // add other videos to .gallery
        model.gallery.items.push(video);
      }
    }
  }

}

In the above snippet we've taken the 5 first videos from the API, set the currently selected video and pushed the rest into a gallery array. The last missing bit is to create the Custom Action that will be triggered when the user selects a different video. We'll call this action select:

action.select = function(evt) {

  // select active video when clicked node has a data-key attribute
  if(evt.args.dataKey) {
     evt.model.activeId = evt.args.dataKey;
     selectActive(evt.model);
  }

}

One thing to note about the above snippet: when we'll click on the video from the carousel we'll call this custom action, therefore we should check that we're also passing the clicked video id in the data-key attribute before we call selectActive function.

Add markup to the Liquid View

Now that the custom action has been set and the data is split, we can select the Liquid View that holds our feature video and add markup to the view property:

<div class="container layout-vertical layout-center-justified m"> 
  <at-carbon-video id="mainVid" src="{{selected.link}}"></at-carbon-video> 
  <div class="mt layout-vertical">
    <div class="font-subhead">{{ selected.title }}</div>    
  </div>
</div>

Here we're using the <at-carbon-video> element and setting its source to be the link property of our currently selected video as well as set the title.

It's time to create the Carousel element and its markup. We'll start by adding a new Carousel element below our feature video:

Add a Carousel element bellow the feature video

We need to set a few properties for the Carousel just like we set them for the Liquid View. Namely, we'll set the Listeners to _all, mode to bound and modelRoot to state.model.gallery. Setting the modelRoot will give the Carousel access to the array of remaining videos that we defined earlier. The mode property defines whether the Carousel should use the data passed into its modelRoot (be bound to data) or just show a static list of entries. In this case we want the Carousel to use the live data.

The Carousel is designed to display multiple items with the same layout. That's why we need to make sure that modelRoot is getting an array as it'll be used to iterate and create Carousel entries. We also need to specify a custom Component that will hold the markup and logic for each repeated item. We'll call this custom component video.card and that's what we'll create next:

Specify a custom component and modelRoot

Create the custom video Component

Now we'll create the custom Component for each entry in our Carousel. First of all, let's save our current Card and then navigate to its root directory. Here we'll create a new file:

Add a new file in the root directory

and we'll name it video.card so it matches the name we specified in Carousel's itemComponent property:

Create new Custom component video.card

Next, we'll open the video.card in the Designer and add a new Liquid View to it. In the Liquid View we'll set the Listeners property to _all, the modelRoot to state.model and the view property to the following markup:

<div class="mr mtsm" class="action" at-click-action="select" data-key="{{ item.ytvideoId }}" >
  <iron-image sizing="cover" alt="{{ item.title }}" width="105" height="80" src="http://i3.ytimg.com/vi/{{ item.ytvideoId }}/maxresdefault.jpg"></iron-image>
      <span class="font-body2"><at-carbon-clamp lines="2">{{ item.title }}</at-carbon-clamp></span>
</div>

Here's what the above snippet does: each element in our Carousel will be rendered as a <div>. Any time a <div> is clicked it will trigger our custom select action that we created earlier via at-click-action property and it will pass the 'id' of the clicked video via the data-key property. We're also rendering a thumbnail image using the <iron-image> element and adding a title. Let's save this component and go back to our Card.

D. Add styling

If we open the Card in the preview we should see something like this:

Card preview before styling

Not bad, but we could add a few minor tweaks to make it look better. For example items in the Carousel are too wide for us to see other thumbnails, so let's reduce the width of each Carousel item by selecting the Carousel and changing the cardWidth property to a smaller value:

Reduce carousel item width

Also, it would be a good idea to set a more relevant message to be shown in the Carousel if no videos are found, thus we'll change the emptyList property to "No videos found".

Furthermore, we'll hide the Carousel when it's not used by adding an expandable footer to the Card which will also have a link to the YouTube channel. Let's go to the Elements tab and add a Footer element. Let's select the Footer and add the following:

  1. Set as expandable
  2. Add a label and href that will point to the YouTube channel.

Set footer settings

And finally to hide the Carousel when the footer is not expanded we'll add 'expanded-only' to the CSS Class property in the Carousel:

Add CSS class to the carousel

Finally, if we look at the preview now we should see that the Carousel is hidden unless the Footer is expanded:

Final Card